C 언어에서 파일을 다룰 때 우리는 FILE이라는 데이터 타입의 포인터를 사용합니다. 즉, FILE은 하나의 데이터 타입인데, 이를 불투명(Opaque) 데이터 타입이라고 부릅니다. 불투명 타입이란 내부 구현이 외부에 숨겨져 있어 사용자가 직접 접근할 수 없는 타입을 의미합니다.
FILE 타입의 실제 정의는 운영체제나 시스템마다 다릅니다. 표준 라이브러리는 FILE의 내부 구조를 감추고, fopen(), fread(), fclose() 같은 함수들을 통해서만 파일을 조작하도록 설계되어 있기 때문입니다. 참고로 Ubuntu(Linux) 시스템에서 glibc 라이브러리에 정의된 FILE 구조체는 다음과 같습니다.
FILE 구조체의 실제 정의 (Ubuntu 기준)
struct _IO_FILE {
int _flags; /* 상위 워드는 _IO_MAGIC, 나머지는 플래그 */
#define _IO_file_flags _flags
/* 아래 포인터들은 C++ streambuf 프로토콜에 대응됨 */
char* _IO_read_ptr; /* 현재 읽기 포인터 */
char* _IO_read_end; /* get 영역의 끝 */
char* _IO_read_base; /* putback+get 영역의 시작 */
char* _IO_write_base; /* put 영역의 시작 */
char* _IO_write_ptr; /* 현재 쓰기 포인터 */
char* _IO_write_end; /* put 영역의 끝 */
char* _IO_buf_base; /* 버퍼 영역의 시작 */
char* _IO_buf_end; /* 버퍼 영역의 끝 */
/* 백업 및 되돌리기를 지원하기 위한 필드들 */
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset;
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
/* 이후 필드 생략 */
};위 구조체에서 눈여겨볼 부분은 _fileno(실제 파일 디스크립터), 읽기·쓰기 위치를 가리키는 포인터들, 그리고 스트림 잠금(_lock) 관련 필드입니다. 이처럼 FILE은 단순히 파일을 가리키는 것 이상으로, 버퍼링 상태, 현재 입출력 위치, 오류 플래그 등 파일 스트림의 모든 상태 정보를 담고 있는 복합적인 자료구조입니다.
FILE 포인터 활용 예제
그럼 실제로 FILE 포인터를 사용해 파일을 읽는 간단한 예제를 살펴보겠습니다. 먼저 다음과 같은 텍스트 파일(/tmp/test.txt)이 있다고 가정합니다.
This is testing for fprintf... This is testing for fputs...
이제 이 파일을 열고 내용을 읽어 출력하는 C 프로그램입니다.
#include <stdio.h>
int main() {
FILE *fp;
char buff[255];
/* 파일을 읽기 모드로 열기 */
fp = fopen("/tmp/test.txt", "r");
fscanf(fp, "%s", buff);
printf("1 : %s\n", buff);
fgets(buff, 255, (FILE*)fp);
printf("2 : %s\n", buff);
fgets(buff, 255, (FILE*)fp);
printf("3 : %s\n", buff);
fclose(fp);
return 0;
}실행 결과
1 : This 2 : is testing for fprintf... 3 : This is testing for fputs...
코드 동작 방식 살펴보기
이 예제의 핵심 동작은 다음과 같습니다.
1. fopen()으로 파일 열기: fopen("/tmp/test.txt", "r")은 파일을 읽기 모드로 열고, 해당 파일 스트림에 대한 FILE 포인터를 반환합니다. 실패 시 NULL을 반환하므로 실무에서는 반드시 널 체크를 하는 것이 좋습니다.
2. fscanf()로 단어 단위 읽기: fscanf(fp, "%s", buff)는 공백이나 개행 문자를 만날 때까지 한 단어씩 읽어옵니다. 따라서 첫 번째 출력 결과는 "This"만 나타납니다.
3. fgets()로 줄 단위 읽기: fgets(buff, 255, fp)는 개행 문자를 포함하여 한 줄 전체를 읽어옵니다. 두 번째 호출에서 남아 있는 줄 전체가 출력되는 이유입니다.
4. fclose()로 파일 닫기: 사용이 끝난 파일 스트림은 반드시 fclose()로 닫아야 버퍼의 내용이 디스크에 반영되고 시스템 자원이 해제됩니다.
정리
C 언어의 FILE은 내부 구현이 숨겨진 불투명(Opaque) 데이터 타입으로, 운영체제마다 정의가 다르지만 공통적으로 파일 스트림의 상태(버퍼, 위치, 플래그 등)를 관리하는 역할을 합니다. 개발자는 FILE 포인터를 통해 표준 라이브러리 함수만으로 파일 입출력을 안전하게 수행할 수 있으며, 이러한 추상화 덕분에 동일한 코드가 다양한 시스템에서 동작할 수 있습니다.