71.4 제한된 버퍼로 파일 전체를 읽기

파일의 크기가 작다면 파일 크기만큼 버퍼를 생성해서 한꺼번에 읽으면 됩니다. 하지만 파일의 크기가 엄청 클 때는 파일 크기만큼 큰 버퍼를 생성하기에는 어려움이 있습니다. 이번에는 파일의 끝인지 검사하는 함수 feof와 부분적으로 파일을 읽는 방법을 이용해서 제한된 버퍼로 파일 전체를 읽어보겠습니다.

feof 함수는 현재 파일 포인터가 파일의 끝인지 검사합니다(stdio.h 헤더 파일에 선언되어 있습니다).

  • feof(파일포인터);
    • int feof(FILE *_Stream);
    • 파일의 끝이면 1, 끝이 아니면 0을 반환

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

read_entire_file_limited_buffer.c

#define _CRT_SECURE_NO_WARNINGS    // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>     // fopen, feof, fread, fclose 함수가 선언된 헤더 파일
#include <string.h>    // strlen, memset 함수가 선언된 헤더 파일

int main()
{
    char buffer[5] = { 0, };    // 문자열 데이터 4바이트 NULL 1바이트. 4 + 1 = 5
    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);    // total: 13: 파일을 읽은 전체 크기 출력

    fclose(fp);    // 파일 포인터 닫기

    return 0;
}

실행 결과

Helabcdworld!
total: 13

이번 예제에서는 크기가 큰 파일을 연다고 가정하겠습니다. 먼저 버퍼 크기를 5바이트로 만들어주는데 문자열을 읽어서 출력할 것이므로 문자열 4바이트, NULL 1바이트를 더해서 5바이트입니다(실제로는 메가바이트 단위로 버퍼를 만들면 됩니다).

char buffer[5] = { 0, };    // 문자열 데이터 4바이트 NULL 1바이트. 4 + 1 = 5

fopen 함수를 사용하여 파일을 읽기 모드(r)로 엽니다.

FILE *fp = fopen("hello.txt", "r");    // hello.txt 파일을 읽기 모드(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에 읽은 크기를 저장해두고 printfbuffer의 내용을 출력합니다. 출력이 끝났으면 memset함수로 buffer의 내용을 0으로 초기화한 뒤 total에 읽은 크기 count를 누적합니다.

    printf("%s", buffer);                          // 읽은 내용 출력
    memset(buffer, 0, 5);                          // 버퍼를 0으로 초기화
    total += count;                                // 읽은 크기 누적
}

이제 파일을 4바이트씩 쪼개서 읽은 뒤 부분 부분 내용을 출력하면 전체 문자열이 출력됩니다(앞에서 "Hello, world!" 중간에 "abcd"를 썼으므로 "Helabcdworld!"가 나옵니다).

Helabcdworld!

hello.txt의 크기가 13바이트이므로 파일을 읽은 전체 크기는 13이 출력됩니다.

printf("\ntotal: %d\n", total);    // total: 13: 파일을 읽은 전체 크기 출력

마지막으로 파일 읽기가 끝났다면 fclose 함수로 파일을 닫아줍니다.

fclose(fp);    // 파일 포인터 닫기

지금까지 제한된 버퍼로 파일을 읽은 과정을 그림으로 표현하면 다음과 같습니다. 즉, fread로 4바이트씩 읽으면서 계속 순방향으로 이동하고, feof(fp)의 반환값이 1이 되면 멈추는 방식입니다.

그림 71‑9 제한된 버퍼로 파일 전체를 읽기

fread(buffer, sizeof(char), 4, fp);에서 첫 번째, 두 번째, 세 번째 호출에서는 4바이트를 읽고 4를 반환하지만, 마지막에는 남은 데이터가 1바이트 밖에 없으므로 1바이트만 읽고 1을 반환합니다.

참고 | feof 함수와 EOF

feof 함수는 현재 파일 포인터의 위치가 파일의 끝이면 1, 파일의 끝이 아니면 0을 반환하므로 -1인 EOF와는 상관이 없습니다. EOFscanf, fscanf 등의 함수에서 값을 읽을 수 없는 상태일 때 반환됩니다.

지금까지 파일 포인터에 대해 배웠는데 눈에 보이지 않는 파일의 내부를 왔다 갔다 해서 이해하기가 쉽지 않았을 겁니다. 아직은 개념을 완벽히 이해하지 않아도 되며 함수 사용 방법만 외워서 사용해도 됩니다. 여기서 파일 포인터를 이동시킬 때는 fseek 함수를 사용한다는 점, fread, fwrite, fputs, fgets 등의 함수는 동작한 후에 파일 포인터가 순방향으로 이동한다는 점만 기억하면 됩니다.