Skip to content

63. 파일 포인터


1. 키워드

  • 파일 포인터
  • fseek(File Seek)
  • ftell(File Tell)
  • feof(File End Of File)


2. 파일 읽기/쓰기 위치 활용하기

  • 지금까지 파일을 읽을 때 파일 크기를 이미 알고 있었기 때문에 버퍼 크기를 파일 크기보다 크게 만들었다.
  • 하지만 실제로는 파일 크기를 미리 알고 코드를 작성하는 건 불가능하다.
  • 또한, 파일의 크기가 작다면 파일을 처음부터 끝까지 읽어도 문제가 없지만 파일이 매우 클 때 처음부터 끝까지 읽는 것은 비효율적이다.
  • 이번에는 파일 포인터를 이동시켜서 파일의 크기를 구하는 방법과 파일을 부분적으로 읽고 쓰는 방법을 알아보자.


3. 파일 크기 구하기

  • 파일의 크기를 구할 때는 fseek, ftell 함수를 사용한다.


fseek(파일포인터, 이동할크기, 기준점);
성공하면 0, 실패하면 -1 반환

ftell(파일포인터);
파일 포인터의 현재 위치를 반환, 실패하면 -1 반환
#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)로 연다.


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


  • fseek 함수는 기준점에서 이동할 거리(크기)를 지정하여 파일 포인터를 이동시킨다.
  • 이후 파일 읽기/쓰기 함수를 사용하면 fseek에서 이동시킨 파일 포인터의 위치부터 읽기/쓰기를 하게 된다.


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


001


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


002


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


  • 만약 현재 위치에서 순방향으로 10바이트 전진하려면 fseek(fp, 10, SEEK_CUR);과 같이 양수를 지정하면 된다.
  • 또한 현재 위치에서 역방향으로 10바이트 후진하려면 fseek(fp, -10, SEEK_CUR);과 같이 음수를 지정하면 된다.


003


  • 파일 포인터의 이동 방향은 포인터 연산에서의 이동 방향과 같다.
  • 즉, 파일의 끝 방향으로 가면 순방향으로 이동, 파일의 처음 방향으로 가면 역방향으로 이동이다.


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


fseek(fp, 0, SEEK_END); // 파일 포인터를 파일의 끝으로 이동시킴
size = ftell(fp);       // 파일 포인터의 현재 위치를 얻음


004


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


005


  • 앞에서 파일의 크기를 구할 때 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 함수로 파일 포인터를 파일 끝, 파일 처음으로 이동시킨 그림이다.


006


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


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


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


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

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


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"가 나온다.


printf("%s\n", buffer);


007


  • 이제 파일의 파일 포인터를 순방향으로 좀 더 옮겨서 읽어보자.
  • 먼저 파일을 읽기 전에 memset 함수를 사용하여 버퍼를 0으로 초기화한다.
  • 그리고 fseek 함수에 3SEEK_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"가 나왔다.


printf("%s\n", buffer);


  • 이전에 fread 함수로 3바이트만큼 "llo"를 읽었는데, 이렇게 되면 파일 포인터도 3바이트만큼 순방향으로 이동한다.
  • 따라서 처음 fseek2바이트 이동하고, 다시 fread3바이트 이동, 마지막으로 fseek3바이트 이동하여 파일 처음에서 순방향으로 8바이트(2 + 3 + 3)지점에 있는 "orld"를 읽게 된다.


008


  • 이번에는 파일에 값을 부분적으로 써보자.


#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!"가 된다.


009


fgets, fputs 함수와 파일 포인터의 위치

  • fgets, fputs 함수도 읽고 쓴 크기만큼 파일 포인터가 순방향으로 이동한다.


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

  • feof 함수는 현재 파일 포인터가 파일의 끝인지 검사한다.


feof(파일포인터);
파일의 끝이면 1, 끝이 아니면 0 반환
#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바이트이다.


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


  • 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바이트씩 쪼개서 읽은 뒤 부분 부분 내용을 출력하면 전체 문자열이 된다.


Helabcdworld!


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


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


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


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


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


010


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


feof 함수와 EOF

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

References