71.2 파일 크기만큼 파일 읽기

'70.4 파일에서 문자열 읽기'에서는 파일 크기와 상관없이 버퍼의 크기를 20바이트만큼 생성했습니다. 하지만 이렇게 되면 파일의 크기가 20바이트를 넘을 때는 내용을 모두 읽을 수 없게 됩니다. 이번에는 앞에서 배운 파일의 크기를 구하는 방법을 이용하여 파일 크기만큼 버퍼를 생성하고, 파일을 읽어보겠습니다.

read_entire_file.c

#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);
                    // Hello world! size: 13, count: 1: 파일의 내용, 파일 크기, 읽은 횟수 출력

    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을 찾을 수 없게 되어 문자열 이외의 값이 함께 출력되므로 주의해야 합니다.

그림 71‑4 버퍼를 생성할 때 NULL이 들어갈 공간까지 확보하고 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 함수를 사용해도 됩니다(stdio.h 헤더 파일에 선언되어 있습니다).

rewind(fp);    // rewind 함수를 사용하여 파일 포인터를 파일의 처음으로 이동시킴
count = fread(buffer, size, 1, fp);    // hello.txt에서 파일 크기만큼 값을 읽음

다음은 fseek 함수로 파일 포인터를 파일 끝, 파일 처음으로 이동시킨 그림입니다.

그림 71‑5 파일 포인터를 파일 처음, 끝으로 이동시키기

이제 printfbuffer의 내용을 출력해보면 hello.txt의 내용인 "Hello,world!"가 출력됩니다.

printf("%s size: %d, count: %d\n", buffer, size, count);
                // Hello world! size: 13, count: 1: 파일의 내용, 파일 크기, 읽은 횟수 출력

파일 읽기가 끝났으면 파일 포인터도 닫고 버퍼에 할당된 동적 메모리도 해제합니다.

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

free(buffer);    // 동적 메모리 해제