71 파일 읽기/쓰기 위치 활용하기

지금까지 파일을 읽을 때 파일 크기를 이미 알고 있었기 때문에 버퍼 크기를 파일 크기보다 크게 만들었습니다. 하지만 실제로는 파일 크기를 미리 알고 코드를 작성하는 건 불가능합니다.

또한, 파일은 값을 모아놓은 큰 덩어리입니다. 파일의 크기가 작다면 파일을 처음부터 끝까지 읽어도 문제가 없지만 파일이 매우 클 때 처음부터 끝까지 읽는 것은 비효율적입니다.

이번에는 파일 포인터를 이동시켜서 파일의 크기를 구하는 방법과 파일을 부분적으로 읽고 쓰는 방법을 알아보겠습니다.

71.1 파일 크기 구하기

먼저 '70.3 파일에 문자열 쓰기'에서 만든 hello.txt 파일의 크기를 구해보겠습니다. 파일의 크기를 구할 때는 fseek, ftell 함수를 사용합니다(stdio.h 헤더 파일에 선언되어 있습니다).

  • fseek(파일포인터, 이동할크기, 기준점);
    • int fseek(FILE *_Stream, long _Offset, int _Origin);
    • 성공하면 0, 실패하면 -1을 반환
  • ftell(파일포인터);
    • long ftell(FILE *_Stream);
    • 파일 포인터의 현재 위치를 반환, 실패하면 -1을 반환

다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요.

file_size.c

#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);      // 13

    fclose(fp);

    return 0;
}

실행 결과

13

파일의 크기를 구할 때도 파일 포인터가 필요합니다. 먼저 fopen 함수를 사용하여 hello.txt 파일을 읽기 모드(r)로 엽니다.

FILE *fp = fopen("hello.txt", "r");    // hello.txt 파일을 읽기 모드(r)로 열기.
                                       // 파일 포인터를 반환

이제 fseek 함수를 알아보겠습니다. fseek는 기준점에서 이동할 거리(크기)를 지정하여 파일 포인터를 이동시킵니다. 이후 파일 읽기/쓰기 함수를 사용하면 fseek에서 이동시킨 파일 포인터의 위치부터 읽기/쓰기를 하게 됩니다.

fseek의 기준점은 다음과 같이 세 가지가 있습니다.

표 71‑1 fseek 함수의 기준점 종류
기준점 설명
SEEK_SET 파일의 처음부터 이동을 시작 fseek(fp, 0, SEEK_SET); // 파일 포인터를 파일의 처음으로 이동시킴
SEEK_CUR 현재 위치부터 이동을 시작 fseek(fp, -10, SEEK_CUR); // 파일 포인터를 현재 위치에서 10바이트만큼 역방향으로 이동시킴(-10이 음수이므로)
SEEK_END 파일의 끝부터 이동을 시작 fseek(fp, 0, SEEK_END); // 파일 포인터를 파일의 끝으로 이동시킴

즉, SEEK_SET은 파일의 처음, SEEK_END는 파일의 끝, SEEK_CUR는 파일 포인터의 현재 위치를 뜻하며 기준점을 기준으로 파일 포인터의 위치를 바꿉니다.

그림 71‑1 fseek 함수의 기준점

만약 fseek(fp, 0, SEEK_SET);이면 파일의 처음으로 갑니다. 즉, 파일의 처음에서 0만큼 이동하면 움직인 거리가 없으므로 파일 포인터는 파일 처음으로 갑니다. 마찬가지로 fseek(fp, 0, SEEK_END);이면 파일 끝에서 0만큼 이동하므로 결국 파일 끝으로 갑니다.

그렇다면 현재 위치에서 순방향으로 10바이트 이동(전진, forword)하려면 어떻게 해야 할까요? fseek(fp, 10, SEEK_CUR);과 같이 SEEK_CUR에서 10바이트만큼 순방향으로 이동하도록 만들면 됩니다. 역방향으로 10바이트 이동(후진, backward)하려면 fseek(fp, -10, SEEK_CUR);과 같이 음수를 지정하면 됩니다.

그림 71‑2 fseek 함수의 사용

파일 포인터의 이동 방향은 포인터 연산에서의 이동 방향과 같습니다. 즉, 파일의 끝 방향으로 가면 순방향으로 이동(파일 포인터의 위치 값이 커짐), 파일의 처음 방향으로 가면 역방향으로 이동입니다(파일 포인터의 위치 값이 작아짐).

다시 코드를 보면 이동할 크기를 0으로, 기준점을 SEEK_END로 지정하여 파일 포인터를 파일의 끝으로 이동시킵니다. 이 상태에서 ftell 함수를 사용하여 파일 포인터의 현재 위치를 얻어오면 파일의 크기를 알 수 있습니다.

fseek(fp, 0, SEEK_END);    // 파일 포인터를 파일의 끝으로 이동시킴
size = ftell(fp);          // 파일 포인터의 현재 위치를 얻음
그림 71‑3 fseek, ftell 함수로 파일 크기 구하기
참고 | SEEK_SET?

파일의 시작점은 SEEK_START나 SEEK_BEGIN이어야 할 텐데 왜 SEEK_SET일까요? start나 begin도 맞는 말이지만 set은 시작되다라는 뜻도 있어서 SEEK_SET으로 만들지 않았나 싶습니다. 

참고 | 파일 포인터

파일 포인터(file pointer)는 사실 여러 가지 의미를 함께 가지고 있습니다. 먼저 다음과 같이 FILE 구조체의 포인터라서 FILE 포인터입니다.

FILE *fp;    // FILE 구조체의 포인터

그리고 실제 파일의 읽기/쓰기 위치를 가리킨다고하여 파일 포인터이기도 합니다.

                     fp
                      파일의 읽기/쓰기 위치를 가리킴(pointer)
파일 |-----------------------------------------------------|