C언어 (21) 파일 입출력2
stream이란 무엇인지, 파일을 어떻게 여는지 알았으니 이제 입출력 함수를 사용해본다.
표준 파일 입출력 함수
표준 파일 입출력 함수는 이렇게 입출력 스트림을 정해서 원하는 입력 방법을 선택할 수 있다.
함수들을 하나씩 사용하면서 기능을 정리해보자.
문자 단위 표준 파일 입출력 함수
fgetc(), fputc()는 문자 한개 단위로 입력과 출력을 하는 함수이다.
함수 원형을 각각 보자.
1
2
3
int fgetc(FILE* stream)
키보드 / 파일로부터 한 문자를 입력 받음
1
2
3
int fputc(int c, FILE* stream)
모니터 / 파일에 한 문자를 출력, c에 출력할 문자를 넣음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* stream;
int input = 0;
stream = fopen("data.txt", "w"); // 파일 열기
if (stream == NULL) {
printf("파일 열기 오류");
}
while (input != EOF) {
input = fgetc(stdin);
fputc(input, stream);
}
fclose(stream); //파일 닫기
return 0;
}
지금 fgetc는 stdin이 인자이므로 키보드로 입력 받고, fputc는 stream이 인자이므로 파일에 출력한다.
EOF가 입력 될때까지 입력을 받는데, 키보드로 EOF는 ctrl+z이다.
입력을 하고 프로그램을 종료한 후 파일 위치로 가서 data.txt를 열어보면 데이터가 잘 들어가있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* stream;
int input = 0;
stream = fopen("data.txt", "r"); // 파일 열기
if (stream == NULL) {
printf("파일 열기 오류");
}
while (input != EOF) {
input = fgetc(stream);
fputc(input, stdout);
}
fclose(stream); //파일 닫기
return 0;
}
이번에는 파일에서 읽어오고, 화면에 출력을 해보자.
파일을 읽기용으로 열어야 하므로, fopen에 “r”을 인자로 줘야 한다.
그 다음 fgetc는 stream, fputc는 stdout으로 하면 data.txt의 내용을 화면에 잘 출력한다.
문자열 단위 표줄 파일 입출력 함수
fgetc와 fputc가 문자 하나를 다뤘다면 fgets와 fputs는 문자열 단위로 입출력을 한다.
사용 방법은 거의 동일하고, fgets()에 입력받을 문자열의 최대 크기를 추가로 줘야 한다.
1
2
3
4
int fgets(char* s, int n, FILE* stream)
키보드 / 파일로부터 문자열을 입력 받음
n으로 입력 받을 문자열의 최대 크기를 정하고, 입력 받은 것을 배열 s에 저장한다.<br>
1
2
3
int fputs(const char* s, FILE* stream)
모니터 / 파일에 문자열을 출력, s의 내용을 지정한 stream으로 출력한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* stream;
char buffer[50];
stream = fopen("data2.txt", "w"); // 파일 열기
if (stream == NULL) {
printf("파일 열기 오류");
}
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
fputs(buffer, stream);
}
fclose(stream); //파일 닫기
return 0;
}
한 줄씩 입력도 잘 된다.
자료형 단위 표준 파일 입출력 함수
sscanf와 sprintf처럼 fscanf와 fprintf는 stream을 통해 데이터를 받는 printf, scanf이다.
1
2
3
int fscanf(FILE* stream, ...scanf와 같은 방식)
키보드 / 파일로부터 자료형에 맞춰 데이터를 입력
1
2
3
int fprintf(FILE* stream, ...printf와 같은 방식)
모니터 / 파일에 자료형에 맞춰 데이터를 출력
밑의 코드를 보면 알 수 있듯이 stream만 정해주고 뒤에는 scanf나 printf와 똑같이 사용한다.
1
2
<data2.txt>
50 60
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* stream;
char buffer[50];
int num1, num2;
stream = fopen("data2.txt", "r"); // 파일 열기
if (stream == NULL) {
printf("파일 열기 오류");
}
fscanf(stream, "%d %d", &num1, &num2);
fprintf(stdout, "%d + %d = %d입니다.", num1, num2, num1 + num2);
fclose(stream); //파일 닫기
return 0;
}
1
50 + 60 = 110입니다.
data2.txt에 50 60이라는 값을 넣고, fscanf를 통해 파일로부터 num1, num2에 입력을 받는다.
이러면 data2.txt의 50 60은 정수형으로 입력이 된 것이고, 계산을 한 뒤 출력이 된다.
바이너리 파일의 입출력
위에서 본 함수들은 텍스트 모드에서 동작하는 함수들이었다.
텍스트 파일 입출력 함수는 텍스트와 몇몇 제어 문자를 숫자로 변환하기 때문에
바이너리 파일에 비해 변환 과정이 생겨 속도가 조금 느려진다.
그렇기에 대량의 데이터에 대해서는 바이너리 모드로 수행하는 것이 좋다.
이진 파일의 입출력에는 fread()와 fwrite()를 사용한다.
1
2
3
4
size_t fread(void *buffer, size_t size, size_t count, File* stream)
파일로부터 바이너리 데이터를 받아 buffer에 입력.
stream이 가리키는 파일로부터 size 만큼을 count가 정한 횟수만큼 입력 받는다.
1
2
3
4
size_t fwrite(const void *buffer, size_t size, size_t count, File* stream)
버퍼에 저장한 데이터를 파일에 출력.
stream이 가리키는 파일에 buffer의 데이터를 size 크기로 count가 정한 횟수만큼 출력한다.
이 함수들은 한꺼번에 많은 데이터를 이용할 때 아주 좋은 성능을 보여준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* stream;
int buffer1[5] = { 0xff, 0x56, 0x78, 0xfa, 0xf1 };
int buffer2[5];
stream = fopen("binary.bin", "wb");
fwrite(buffer1, sizeof(int), 5, stream);
fclose(stream);
stream = fopen("binary.bin", "rb");
fread(buffer2, sizeof(int), 5, stream);
for (int i = 0; i < 5; i++) {
printf("%x ", buffer2[i]);
}
fclose(stream);
return 0;
}
buffer1의 데이터를 binary.bin이라는 파일에 옮기고, 그 파일을 읽어 buffer2로 옮기는 함수다.
바이너리 파일을 여는 것이므로 fopen의 인자가 w, r이 아닌 wb, rb로 해줘야 한다.
나머지는 위의 코드와 크게 다른 점은 없는 것 같다. 이렇게 실행하고 나온 결과는
이렇게 binary.bin 파일이 생겼고, 값이 buffer2에 잘 들어갔다.
지금 bin 파일을 압축 파일이라고 컴퓨터가 읽고 있는데 메모장으로 binary.bin을 열어보면
buffer1의 이진수가 아스키코드로 변환되어 알 수 없는 값들로 변환되어 저장되어 있다.
binary 파일은 사람이 읽을 수 없는 파일이기에 fread나 fwrite 같은 함수로 다뤄야만 한다.
파일의 중간에 접근
지금까지 파일 입출력 함수는 처음부터 끝까지 데이터를 입력받고 출력하는 순차 접근 방식이었다.
하지만 필요한 파일이 중간 또는 맨끝에 있다면 시간을 낭비하는 것이다.
그래서 사용자가 원하는 데이터가 있는 곳으로 파일 포인터를 옮기는 함수가 있다.
이것이 fseek()과 ftell() 함수이고, 임의 접근 방식이라고 부른다.
우선 fseek() 함수부터 보면
1
2
3
int fseek(File *stream, long offset, int start);
start부터 offset까지 스트림을 이동시킨다.
offset은 이동할 바이트 크기이고 start에는 숫자가 들어가는 것이 아니라
SEEK_SET | SEEK_CUR | SEEK_END |
---|---|---|
0 | 1 | 2 |
파일의 시작 위치 | 파일의 현재 위치 | 파일의 끝 위치 |
SEEK_SET, SEEK_CUR, SEEK_END 3개 중 1개가 들어간다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* stream;
stream = fopen("seek.txt", "w");
fputs("ABCDEFGHIJ", stream);
fclose(stream);
stream = fopen("seek.txt", "r");
fprintf(stdout, "%c \n", fgetc(stream)); //1
fseek(stream, 2, SEEK_SET);
fprintf(stdout, "%c \n", fgetc(stream)); //2
fseek(stream, -2, SEEK_END);
fprintf(stdout, "%c \n", fgetc(stream)); //3
fseek(stream, -3, SEEK_CUR);
fprintf(stdout, "%c \n", fgetc(stream)); //4
return 0;
}
1
2
3
4
A
C
I
G
우선 seek.txt에 AVCDEFGHIJ라는 값을 넣었다.
어떻게 움직인 것인지 하나씩 보면, 우선 첫 번째는 fgetc로 한 글자 읽었으니 A가 출력된다.
두 번째는 SEEK_SET(파일의 처음)으로부터 2만큼 움직였기에 C가 출력된다.
세 번째는 SEEK_END(파일의 끝)으로부터 -2만큼 움직였기에 I가 출력된다.
이 때 SEEK_END는 EOF를 가리키고 있다.
마지막으로 SEEK_CUR(현재 위치)로부터 -3만큼 움직여서 G가 출력되었다.
세 번째에서 fgetc로 I를 출력하면서 포인터가 한 칸 움직이기 때문에 SEEK_CUR은 J를 가리키고 있었다.
여기에서 -3만큼 움직이므로 G가 출력되는 것이다.
fseek을 이용하면 파일을 덮어쓰는 것도 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* stream;
stream = fopen("seek.txt", "w");
fputs("ABCDEFGHIJ", stream);
fseek(stream, 5, SEEK_SET);
fputs("Hello", stream);
fclose(stream);
return 0;
}
ABCDEFGHIJ가 seek.txt에 적혀있었지만 파일 포인터를 처음 위치에서 5만큼 떨어진 곳으로 옮긴다.
거기서부터 Hello라는 문자열을 넣으면 seek.txt의 내용이 덮어씌워진다.
1
ABCDEHello
ftell() 함수는 파일 포인터가 시작부터 얼마나 떨어져 있는지를 알려주는 함수이다.
1
long ftell(FILE* stream)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* stream;
stream = fopen("seek.txt", "w");
fputs("ABCDEFGHIJ", stream);
fclose(stream);
stream = fopen("seek.txt", "r");
fseek(stream, 0, SEEK_END);
printf("파일의 크기 : %d", ftell(stream));
return 0;
}
1
파일의 크기 : 10
파일 포인터를 맨 끝으로 놓고, ftell 함수를 사용하면 파일의 크기를 쉽게 구할 수 있다.
이렇게 파일 포인터를 옮기고, 내용을 적는 입출력 함수에 대해 다 알아봤다.
다음은 프로그램을 실행하면서 메모리를 할당하는 동적 할당을 알아보자.