C로 2GiB 이상 크기의 파일 다루기

C로 2GiB 이상 크기의 파일을 다루는 방법을 간단히 기록한다.

C 파일 API

익히 알고 있듯이 C 언어에서는 파일을 다룰 때 아래와 같은 2가지 종류의 함수를 사용할 수 있다.

  • 저수준 함수: open(), close(), read(), write(), lseek() 등
  • 고수준 함수: fopen(), fclose(), fread(), fwrite(), fseek() 등

    참고로 저수준과 고수준의 가장 큰 차이점은 저수준 함수는 OS 버퍼링을 사용하지 않고, 고수준 함수는 OS 버퍼링을 사용한다는 것이다.

C 파일 API에서 오프셋 크기

C 파일 API 중에서 특히 파일의 오프셋을 옮기는 API에서 오프셋 타입의 크기가 중요한데, 아래 선언에서 보듯이 오프셋 값으로 lseek()는 off_t 타입을 사용하고 fseek()는 long 타입을 사용한다.

off_t lseek(int fd, off_t offset, int whence);
int fseek(FILE *stream, long offset, int whence);

그런데 C에서 long 타입은 아래 표에서 보듯이 32비트/64비트 시스템에서 서로 다른 길이와 이에 따른 값의 범위를 갖는다.

자료형 시스템 값 범위
long 32비트 -231 ~ 231-1
64비트 -263 ~ 263-1

즉, 32비트 시스템에서는 long의 범위는 -231 ~ 231-1 이고, 이로 인해 max 값이 231-1 제한을 갖게 된다.

또 아래와 같이 선언되 fseeko() API가 있는데, 이 함수는 컴파일시 _LARGEFILE_SOURCE_FILE_OFFSET_BITS=64 정의가 enable 되면 사용할 수 있다.

int fseeko(FILE *stream, off_t offset, int whence);

이 함수는 위에서 보듯이 오프셋 타입이 off_t로 lseek()와 동일함을 알 수 있다.


lseek(), fseeko() 함수 등에서 사용된 off_t 타입을 조사해 보면 _FILE_OFFSET_BITS 정의 여부에 따라 아래와 같이 타입으로 정의되어 있다.

  • _FILE_OFFSET_BITS=64 정의된 경우: 32비트 시스템에서는 long long(즉 64비트), 64비트 시스템에서는 long(즉 64비트)
  • _FILE_OFFSET_BITS=64 정의되지 않은 경우: long(즉 32비트 시스템에서는 32비트, 64비트 시스템에서는 64비트)

따라서 결론적으로 컴파일시에 _LARGEFILE_SOURCE, _FILE_OFFSET_BITS=64 옵션을 추가하면 시스템에 관계없이 항상 64bit 오프셋 크기를 가질 수 있음을 알 수 있다.

C로 2GiB 이상 파일의 크기 얻기

C 언어로 파일의 크기를 얻는 방법에는 여러가지가 있는데, 예를 들어 아래와 같이 파일의 상태를 얻는 함수들을 이용할 수 있다.

int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);

위에서 출력 파라미터로 얻는 stat 구조체 중에서 st_size 멤버는 off_t 타입으로 선언되어 있다. 따라서 위에서 살펴본 바와 마찬가지로, 컴파일시에 _LARGEFILE_SOURCE, _FILE_OFFSET_BITS=64 옵션을 추가하면 시스템에 관계없이 항상 2GiB 이상의 크기도 올바로 얻을 수 있음을 알 수 있다.

예를 들어, 아래와 같이 파일의 크기를 얻을 수 있다. (아래에서 off_t 타입의 값을 출력하기 위하여 %jd로 포매팅 하였음, 참고로 size_t 타입인 경우에는 %zd로 포매팅하면 됨)

struct stat st;
if (stat(file_name, &st) != 0)
{
    printf("Fail to get file info\n");
    return -1;
}
printf("File size: %jd bytes\n", st.st_size);

위와 같은 동일한 코드와 컴파일 옵션으로 MinGW, Linux에서 둘 다 2GiB 이상의 파일을 정상적으로 다룰 수 있었다.

파일 크기를 문자열로 출력

참고로 파일의 크기를 숫자로만 출력하면 보기가 불편하여, 아래와 같이 입력 숫자에 대하여 1000 자리수마다 , 기호를 넣은 문자열을 리턴하는 함수를 작성하였다.

char *print_num_to_str(long long num)
{
    static char integer_comma_str[30];
    char integer_str[30];
    int integer_len, i, j;
    
    /* 값을 문자열로 변환한다. */
    sprintf(integer_str, "%lld", num);
    
    /* 정수 문자열에서 1000 자리수 마다 ',' 기호를 넣어서 integer_comma_str[]에 넣는다. */
    integer_len = strlen(integer_str);
    memset(integer_comma_str, 0, sizeof(integer_comma_str));
    for (i = 0, j = integer_len + ((integer_len - 1) / 3) - 1; i < integer_len; i++, j--)
    {
        integer_comma_str[j] = integer_str[integer_len - i - 1];
        if (j > 0 && (i + 1) % 3 == 0)
        {
            integer_comma_str[--j] = ',';
        }
    }
    
    return integer_comma_str;
}

이제 아래와 같이 위 함수를 호출하여 파일의 크기를 출력하니 한결 보기가 좋아졌다.

struct stat st;
if (stat(file_name, &st) != 0)
{
    printf("Fail to get file info\n");
    return -1;
}
printf("File size: %s bytes\n", print_num_to_str((long long)st.st_size));

카테고리:

업데이트: