63. 파일 포인터
1. 키워드
- 파일 포인터
fseek
(File Seek)ftell
(File Tell)feof
(File End Of File)
2. 파일 읽기/쓰기 위치 활용하기
- 지금까지 파일을 읽을 때 파일 크기를 이미 알고 있었기 때문에 버퍼 크기를 파일 크기보다 크게 만들었다.
- 하지만 실제로는 파일 크기를 미리 알고 코드를 작성하는 건 불가능하다.
- 또한, 파일의 크기가 작다면 파일을 처음부터 끝까지 읽어도 문제가 없지만 파일이 매우 클 때 처음부터 끝까지 읽는 것은 비효율적이다.
- 이번에는 파일 포인터를 이동시켜서 파일의 크기를 구하는 방법과 파일을 부분적으로 읽고 쓰는 방법을 알아보자.
3. 파일 크기 구하기
- 파일의 크기를 구할 때는
fseek
,ftell
함수를 사용한다.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fseek, ftell, fclose 함수가 선언된 헤더 파일
int main()
{
int size;
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
// 파일 포인터를 반환
fseek(fp, 0, SEEK_END); // 파일 포인터를 파일의 끝으로 이동시킴
size = ftell(fp); // 파일 포인터의 현재 위치를 얻음
printf("%d\n", size);
fclose(fp);
return 0;
}
// 13
- 파일의 크기를 구할 때도 파일 포인터가 필요하다.
- 먼저
fopen
함수를 사용하여hello.txt
파일을 읽기 모드(r
)로 연다.
fseek
함수는 기준점에서 이동할 거리(크기)를 지정하여 파일 포인터를 이동시킨다.- 이후 파일 읽기/쓰기 함수를 사용하면
fseek
에서 이동시킨 파일 포인터의 위치부터 읽기/쓰기를 하게 된다.
fseek
의 기준점은 다음과 같이 세 가지가 있다.
- 즉,
SEEK_SET
은 파일의 처음,SEEK_END
는 파일의 끝,SEEK_CUR
는 파일 포인터의 현재 위치를 뜻하며 기준점을 기준으로 파일 포인터의 위치를 바꾼다.
- 만약
fseek(fp, 0, SEEK_SET);
이면 파일의 처음으로 간다. - 즉, 파일의 처음에서
0
만큼 이동하면 움직인 거리가 없으므로 파일 포인터는 파일 처음으로 간다. - 마찬가지로
fseek(fp, 0, SEEK_END);
이면 파일 끝에서0
만큼 이동하므로 결국 파일 끝으로 간다.
- 만약 현재 위치에서 순방향으로
10
바이트 전진하려면fseek(fp, 10, SEEK_CUR);
과 같이 양수를 지정하면 된다. - 또한 현재 위치에서 역방향으로
10
바이트 후진하려면fseek(fp, -10, SEEK_CUR);
과 같이 음수를 지정하면 된다.
- 파일 포인터의 이동 방향은 포인터 연산에서의 이동 방향과 같다.
- 즉, 파일의 끝 방향으로 가면 순방향으로 이동, 파일의 처음 방향으로 가면 역방향으로 이동이다.
- 다시 코드를 보면 이동할 크기를
0
으로, 기준점을SEEK_END
로 지정하여 파일 포인터를 파일의 끝으로 이동시킨다. - 이 상태에서
ftell
함수를 사용하여 파일 포인터의 현재 위치를 얻어오면 파일의 크기를 알 수 있다.
4. 파일 크기만큼 파일 읽기
- 이번에는 파일의 크기를 구하는 방법을 이용하여 파일 크기만큼 버퍼를 생성하고, 파일을 읽어보자.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fseek, ftell, fread, fclose 함수가 선언된 헤더 파일
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
#include <string.h> // memset 함수가 선언된 헤더 파일
int main()
{
char *buffer;
int size;
int count;
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
// 파일 포인터를 반환
fseek(fp, 0, SEEK_END); // 파일 포인터를 파일의 끝으로 이동시킴
size = ftell(fp); // 파일 포인터의 현재 위치를 얻음
buffer = malloc(size + 1); // 파일 크기 + 1바이트(문자열 마지막의 NULL)만큼 동적 메모리 할당
memset(buffer, 0, size + 1); // 파일 크기 + 1바이트만큼 메모리를 0으로 초기화
fseek(fp, 0, SEEK_SET); // 파일 포인터를 파일의 처음으로 이동시킴
count = fread(buffer, size, 1, fp); // hello.txt에서 파일 크기만큼 값을 읽음
printf("%s size: %d, count: %d\n", buffer, size, count); // 파일의 내용, 파일 크기, 읽은 횟수 출력
fclose(fp); // 파일 포인터 닫기
free(buffer); // 동적 메모리 해제
return 0;
}
// Hello, world! size: 13, count: 1
- 먼저
hello.txt
파일을 읽기 모드(r
)로 열고, 파일의 크기를 구한다.
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
// 파일 포인터를 반환
fseek(fp, 0, SEEK_END); // 파일 포인터를 파일의 끝으로 이동시킴
size = ftell(fp); // 파일 포인터의 현재 위치를 얻음
- 파일의 크기를 구했으니 버퍼를 생성한다.
- 여기서 주의할 점은 파일의 문자열을 읽어서 C 문자열로 만들 때는 문자열 마지막의
NULL
공간까지 확보해야 한다는 점이다. - 따라서
파일 크기 + 1
바이트만큼 메모리를 할당한다. - 그리고
malloc
함수로 할당한 메모리는 이전에 사용하던 값이 지워지지 않고 남아있으므로memset
함수를 사용하여0
으로 초기화해 준다.
buffer = malloc(size + 1); // 파일 크기 + 1바이트(문자열 마지막의 NULL)만큼 동적 메모리 할당
memset(buffer, 0, size + 1); // 파일 크기 + 1바이트만큼 메모리를 0으로 초기화
- 할당한 메모리를
NULL
(0
)으로 초기화하지 않으면 문자열 끝 부분에 다른 값이 들어있을 수도 있다. - 이 상태에서는 문자열의 끝인
NULL
을 찾을 수 없게 되어 문자열 이외의 값이 함께 출력되므로 주의해야 한다.
- 앞에서 파일의 크기를 구할 때
fseek
함수로 파일 포인터를 파일의 끝으로 이동시켰다. - 이 상태에서
fread
함수로 파일을 읽으면 파일의 끝 부분부터 읽게 되므로 아무 내용도 나오지 않는다. - 따라서
fseek
함수에 이동할 크기는0
, 기준점은SEEK_SET
으로 지정하여 파일 포인터를 파일의 맨 처음으로 이동시킨 뒤fread
함수로 파일을 읽는다.
fseek(fp, 0, SEEK_SET); // 파일 포인터를 파일의 처음으로 이동시킴
count = fread(buffer, size, 1, fp); // hello.txt에서 파일 크기만큼 값을 읽음
- 파일 포인터를 파일의 처음으로 이동시킬 때
fseek(fp, 0, SEEK_SET);
대신rewind
함수를 사용해도 된다.
rewind(fp); // rewind 함수를 사용하여 파일 포인터를 파일의 처음으로 이동시킴
count = fread(buffer, size, 1, fp); // hello.txt에서 파일 크기만큼 값을 읽음
- 다음은
fseek
함수로 파일 포인터를 파일 끝, 파일 처음으로 이동시킨 그림이다.
- 이제
printf
로buffer
의 내용을 출력해 보면hello.txt
의 내용인"Hello, world!"
가 출력된다.
- 파일 읽기가 끝났으면 파일 포인터도 닫고, 버퍼에 할당된 동적 메모리도 해제한다.
5. 파일을 부분적으로 읽고 쓰기
- 이번에는
fseek
함수를 사용하여 파일 포인터의 위치를 설정한 뒤 파일의 내용을 부분적으로 읽어보자.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fseek, fread, fclose 함수가 선언된 헤더 파일
#include <string.h> // memset 함수가 선언된 헤더 파일
int main()
{
char buffer[10] = {
0,
};
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
// 파일 포인터를 반환
fseek(fp, 2, SEEK_SET); // 파일 포인터를 파일 처음에서 2바이트만큼 순방향으로 이동시킴
fread(buffer, 3, 1, fp); // 3바이트만큼 읽음. 3바이트만큼 순방향으로 이동
printf("%s\n", buffer);
memset(buffer, 0, 10); // 버퍼를 0으로 초기화
fseek(fp, 3, SEEK_CUR); // 파일 포인터를 현재 위치에서 3바이트만큼 순방향으로 이동시킴
fread(buffer, 4, 1, fp); // 4바이트만큼 읽음. 4바이트만큼 순방향으로 이동
printf("%s\n", buffer);
fclose(fp); // 파일 포인터 닫기
return 0;
}
// llo
// orld
fopen
함수로hello.txt
파일을 열고 파일 포인터(읽기/쓰기 위치)를 파일 처음에서2
바이트만큼 순방향으로 이동시켰다.- 그리고
fread
함수로3
바이트만큼 읽어서 버퍼에 저장한다.
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
// 파일 포인터를 반환
fseek(fp, 2, SEEK_SET); // 파일 포인터를 파일 처음에서 2바이트만큼 순방향으로 이동시킴
fread(buffer, 3, 1, fp); // 3바이트만큼 읽음. 3바이트만큼 순방향으로 이동
- 버퍼의 내용을 출력해 보면
"Hello, world!"
에서 순방향으로2
바이트 지점에 있는"llo"
가 나온다.
- 이제 파일의 파일 포인터를 순방향으로 좀 더 옮겨서 읽어보자.
- 먼저 파일을 읽기 전에
memset
함수를 사용하여 버퍼를0
으로 초기화한다. - 그리고
fseek
함수에3
과SEEK_CUR
을 지정하여 파일 포인터를 현재 위치에서3
바이트만큼 순방향으로 이동시키고,fread
함수로4
바이트만큼 읽어서 버퍼에 저장한다.
memset(buffer, 0, 10); // 버퍼를 0으로 초기화
fseek(fp, 3, SEEK_CUR); // 파일 포인터를 현재 위치에서 3바이트만큼 순방향으로 이동시킴
fread(buffer, 4, 1, fp); // 4바이트만큼 읽음. 4바이트만큼 순방향으로 이동
- 버퍼의 내용을 출력해 보면
"Hello, world!"
에서", wo"
가 나와야 할 것 같은데"orld"
가 나왔다.
- 이전에
fread
함수로3
바이트만큼"llo"
를 읽었는데, 이렇게 되면 파일 포인터도3
바이트만큼 순방향으로 이동한다. - 따라서 처음
fseek
로2
바이트 이동하고, 다시fread
로3
바이트 이동, 마지막으로fseek
로3
바이트 이동하여 파일 처음에서 순방향으로8
바이트(2 + 3 + 3
)지점에 있는"orld"
를 읽게 된다.
- 이번에는 파일에 값을 부분적으로 써보자.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, fseek, rewind, fread, fclose 함수가 선언된 헤더 파일
#include <string.h> // strlen, memset 함수가 선언된 헤더 파일
int main()
{
char *s1 = "abcd";
char buffer[20] = {
0,
};
FILE *fp = fopen("hello.txt", "r+"); // hello.txt 파일을 읽기/쓰기 모드(r+)로 열기.
// 파일 포인터를 반환
fseek(fp, 3, SEEK_SET); // 파일 포인터를 파일 처음에서 3바이트만큼 순방향으로 이동시킴
fwrite(s1, strlen(s1), 1, fp); // 문자열 길이만큼 문자열을 파일에 저장
rewind(fp); // 파일 포인터를 파일의 맨 처음으로 이동 시킴
fread(buffer, 20, 1, fp); // 20바이트만큼 읽음
printf("%s\n", buffer);
fclose(fp); // 파일 포인터 닫기
return 0;
}
// Helabcdworld!
- 먼저
fopen
함수에"r+"
를 지정하여 파일을 읽기/쓰기 모드로 연다. - 그리고
fseek
함수로 파일 포인터를 파일 처음에서3
바이트만큼 순방향으로 이동시킨 뒤fwrite
함수로s1
을 파일에 쓴다.
char *s1 = "abcd";
char buffer[20] = {
0,
};
FILE *fp = fopen("hello.txt", "r+"); // hello.txt 파일을 읽기/쓰기 모드(r+)로 열기.
// 파일 포인터를 반환
fseek(fp, 3, SEEK_SET); // 파일 포인터를 파일 처음에서 3바이트만큼 순방향으로 이동시킴
fwrite(s1, strlen(s1), 1, fp); // 문자열 길이만큼 문자열을 파일에 저장
- 다시 파일 포인터를 파일의 맨 처음으로 이동시킨 뒤
fread
함수로 읽어서 값을 출력해 보면"Helabcdworld!"
가 나온다.
rewind(fp); // 파일 포인터를 파일의 맨 처음으로 이동 시킴
fread(buffer, 20, 1, fp); // 20바이트만큼 읽음
printf("%s\n", buffer);
- 즉, 다음과 같이
"Hello, world!"
의 처음부터 순방향으로3
바이트 지점에"abcd"
를 썼으므로"Helabcdworld!"
가 된다.
fgets
, fputs
함수와 파일 포인터의 위치
fgets
,fputs
함수도 읽고 쓴 크기만큼 파일 포인터가 순방향으로 이동한다.
6. 제한된 버퍼로 파일 전체를 읽기
feof
함수는 현재 파일 포인터가 파일의 끝인지 검사한다.
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h> // fopen, feof, fread, fclose 함수가 선언된 헤더 파일
#include <string.h> // strlen, memset 함수가 선언된 헤더 파일
int main()
{
char buffer[5] = { // 문자열 데이터 4바이트 NULL 1바이트. 4 + 1 = 5
0,
};
int count = 0;
int total = 0;
FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
// 파일 포인터를 반환
while (feof(fp) == 0) // 파일 포인터가 파일의 끝이 아닐 때 계속 반복
{
count = fread(buffer, sizeof(char), 4, fp); // 1바이트씩 4번(4바이트) 읽기
printf("%s", buffer); // 읽은 내용 출력
memset(buffer, 0, 5); // 버퍼를 0으로 초기화
total += count; // 읽은 크기 누적
}
printf("\ntotal: %d\n", total); // 파일을 읽은 전체 크기 출력
fclose(fp); // 파일 포인터 닫기
return 0;
}
// Helabcdworld!
// total: 13
- 먼저 버퍼 크기를
5
바이트로 만들어주는데 문자열을 읽어서 출력할 것이므로 문자열4
바이트,NULL
1
바이트를 더해서5
바이트이다.
fopen
함수를 사용해서 파일을 읽기 모드(r
)로 연다.
while
반복문에 조건식으로feof(fp) == 0
을 지정하여 파일 포인터가 파일의 끝이 아닐 때 계속 반복한다.fread
함수로 파일을 읽을 때는buffer
를 선언한 자료형이char
타입이므로sizeof(char)
를 지정하여1
바이트 크기로4
번 읽는다.- 이렇게 하면
fread
로 파일을 읽었을 때 읽은 크기만큼 반환값이 나오게 되므로 파일을 읽은 전체 크기를 구할 수 있다.
while (feof(fp) == 0) // 파일 포인터가 파일의 끝이 아닐 때 계속 반복
{
count = fread(buffer, sizeof(char), 4, fp); // 1바이트씩 4번(4바이트) 읽기
fread
로 파일을 읽었다면count
에 읽은 크기를 저장해 두고printf
로buffer
의 내용을 출력한다.- 출력이 끝났으면
memset
함수로buffer
의 내용을0
으로 초기화한 뒤total
에 읽은 크기count
를 누적한다.
printf("%s", buffer); // 읽은 내용 출력
memset(buffer, 0, 5); // 버퍼를 0으로 초기화
total += count; // 읽은 크기 누적
}
- 이제 파일을
4
바이트씩 쪼개서 읽은 뒤 부분 부분 내용을 출력하면 전체 문자열이 된다.
hello.txt
의 크기가13
바이트이므로 파일을 읽은 전체 크기는13
이 출력된다.
- 마지막으로 파일 읽기가 끝났다면
fclose
함수로 파일을 닫아준다.
- 지금까지 제한된 버퍼로 파일을 읽은 과정을 그림으로 표현하면 다음과 같다.
- 즉,
fread
로4
바이트씩 읽으면서 계속 순방향으로 이동하고,feof(fp)
의 반환값이1
이 되면 멈추는 방식이다.
fread(buffer, sizeof(char), 4, fp);
에서 첫 번째, 두 번째, 세 번째 호출에서는4
바이트를 읽고4
를 반환하지만, 마지막에는 남은 데이터가1
바이트 밖에 없으므로1
바이트만 읽고1
을 반환한다.
feof
함수와 EOF
feof
함수는 현재 파일 포인터의 위치가 파일의 끝이면1
, 파일의 끝이 아니면0
을 반환하므로-1
인EOF
와는 상관이 없다.EOF
는scanf
,fscanf
등의 함수에서 값을 읽을 수 없는 상태일 때 반환된다.