FILE

FILE

파일 입출력을 하려면 반드시 거치는 타입이 있어요. 그게 바로 FILE이에요. C에서 파일 스트림을 다룰 때 이 포인터를 함수 인자로 계속 넘기면서 작업해요.

출처: cppreference

본문

정의 (Definition)

typedef /* unspecified */ FILE;

FILE 객체 하나가 하나의 C 스트림을 나타내요. FILE이 완전한(complete) 객체 타입인지는 표준이 정하지 않았어요. 그래서 유효한 FILE을 복사할 수도 있겠지만, 그 복사본의 포인터를 입출력 함수 인자로 쓰면 지정되지 않은(unspecified) 동작이 돼요. 쉽게 말해 FILE은 개념적으로 복사할 수 없는 타입이라고 보면 돼요.

입출력 스트림은 형식이 없는(unformatted) 입출력과 형식이 있는(formatted) 입출력 양쪽에 모두 쓸 수 있어요. 또 입력·출력을 다루는 함수는 로케일에 민감해서, 필요하면 와이드/멀티바이트 변환이 알아서 이뤄지기도 해요.

스트림 상태 (Stream state)

장치에 접근하는 데 필요한 시스템별 정보(예: POSIX 파일 디스크립터) 말고도, FILE 객체는 직접 또는 간접적으로 다음을 담고 있어요.

  • (C95) 문자 폭: 설정 안 됨, 좁은 문자, 와이드 문자
  • (C95) 멀티바이트와 와이드 문자 사이 변환을 위한 파싱 상태(mbstate_t 타입의 객체)
  • 버퍼링 상태: 버퍼링 안 함, 줄 버퍼링, 완전 버퍼링
  • 버퍼. 외부에서 만든 사용자 버퍼로 교체될 수도 있어요
  • 입출력 모드: 입력, 출력, 또는 갱신(update, 입력·출력 모두)
  • 이진/텍스트 모드 표시
  • 파일 끝(end-of-file) 상태 표시기
  • 오류 상태 표시기
  • 파일 위치 표시기. fpos_t 타입의 객체로 접근하며, 와이드 스트림의 경우 파싱 상태까지 포함해요
  • (C11) 재진입 가능한 잠금(reentrant lock). 여러 스레드가 동시에 같은 스트림을 읽거나 쓰거나 위치를 옮기거나 조회할 때 데이터 경쟁을 막아줘요

갱신(update) 입출력 모드인 스트림에서는 두 가지 규칙을 지켜야 해요.

  • 출력 연산에서 입력 연산으로 전환하려면, 그 사이에 fflush나 파일 위치 함수(fseek, fsetpos, rewind 중 하나) 호출이 있어야 해요
  • 입력 연산에서 출력 연산으로 전환할 때, 입력 연산이 파일 끝(EOF)을 만나지 않았다면 그 사이에 파일 위치 함수 호출이 있어야 해요

좁은 문자 / 와이드 문자 지향 (Narrow and wide orientation)

갓 연 스트림은 아직 지향(orientation)이 없어요. fwide나 어떤 입출력 함수든 첫 번째 호출이 지향을 결정해요. 와이드 입출력 함수를 호출하면 스트림이 와이드 지향이 되고, 좁은 입출력 함수를 호출하면 좁은 지향이 돼요. 한 번 정해지면 지향은 freopen으로만 바꿀 수 있어요. 와이드 지향 스트림에서는 좁은 입출력 함수를, 좁은 지향 스트림에서는 와이드 입출력 함수를 호출할 수 없어요.

와이드 입출력 함수는 mbrtowcwcrtomb를 호출하는 것처럼 와이드 문자와 멀티바이트 문자를 변환하는데, 스트림이 갖고 있는 변환 상태를 그대로 사용해요. 프로그램 안에서 유효한 멀티바이트 문자열과 달리, 파일 안의 멀티바이트 문자 시퀀스는 널 문자를 중간에 포함할 수 있고, 초기 시프트 상태에서 시작하거나 끝날 필요도 없어요.

와이드 지향 스트림의 변환 상태는 스트림의 지향이 정해지는 시점에 설치된 C 로케일에 의해 확정돼요.

이진 모드와 텍스트 모드 (Binary and text modes)

텍스트 스트림은 줄로 묶을 수 있는 문자들의 순서 있는 나열이에요. 줄은 0개 이상의 문자에 종료 '\n'(뉴라인) 문자를 더한 것으로 나눌 수 있어요. 마지막 줄에 종료 '\n'이 필요한지는 구현에서 정의해요. 여기에 더해, 운영체제에서 텍스트를 표현하는 관례에 맞추려고 입력·출력 시 문자가 추가되거나, 바뀌거나, 삭제될 수 있어요. 특히 Windows의 C 스트림은 출력할 때 '\n''\r\n'으로 바꾸고, 입력할 때는 '\r\n''\n'으로 바꿔요.

텍스트 스트림에서 읽은 데이터가 아까 그 스트림에 써 넣었던 데이터와 완전히 같다고 보장되려면 다음 조건이 모두 참이어야 해요.

  • 데이터가 인쇄 가능한 문자와/또는 제어 문자 '\t', '\n'만으로 이뤄져 있어야 해요(특히 Windows에서는 '\x1A' 문자 하나가 입력을 끝내요)
  • 어떤 '\n' 문자 바로 앞에 공백 문자가 없어야 해요(그런 공백 문자는 나중에 그 출력을 입력으로 읽을 때 사라질 수 있어요)
  • 마지막 문자가 '\n'이어야 해요

이진 스트림은 내부 데이터를 투명하게 기록할 수 있는 문자들의 순서 있는 나열이에요. 이진 스트림에서 읽은 데이터는 예전에 그 스트림에 써 넣었던 데이터와 항상 같아요. 단, 구현이 스트림 끝에 개수가 정해지지 않은 널 문자를 붙이는 건 허용해요. 와이드 이진 스트림은 초기 시프트 상태로 끝날 필요가 없어요.

참고 (Notes)

POSIX는 스트림의 지향이 와이드로 정해지는 순간, 현재 설치된 C 로케일의 LC_CTYPE 요소를 FILE 객체 안에 저장할 것을 명시적으로 요구해요. 그리고 지향이 바뀔 때까지는 이후 setlocale 호출과 무관하게 이 LC_CTYPE 요소를 그 스트림의 모든 향후 입출력에 사용해야 한다고 요구해요.

각 텍스트 줄은 본질적으로 사람이 읽을 수 있는 데이터로 구성되는 게 의도예요. POSIX 구현은 텍스트 스트림과 이진 스트림을 구분하지 않아요('\n'이나 다른 문자에 특별한 변환을 적용하지 않아요).

참고 문헌 (References)

  • C23 표준 (ISO/IEC 9899:2024): 7.24 Input/output <stdio.h> (p: 321-355), 7.31 Extended multibyte and wide character utilities <wchar.h> (p: 415-450)
  • C17 표준 (ISO/IEC 9899:2018): 7.21 Input/output <stdio.h> (p: 217-247), 7.29 Extended multibyte and wide character utilities <wchar.h> (p: 295-325)
  • C11 표준 (ISO/IEC 9899:2011): 7.21 Input/output <stdio.h> (p: 296-339), 7.29 Extended multibyte and wide character utilities <wchar.h> (p: 402-446)
  • C99 표준 (ISO/IEC 9899:1999): 7.19 Input/output <stdio.h> (p: 262-305), 7.24 Extended multibyte and wide character utilities <wchar.h> (p: 348-392)
  • C89/C90 표준 (ISO/IEC 9899:1990): 4.9 INPUT/OUTPUT <stdio.h>

더 알아보기

  • FILE* 타입의 표준 스트림인 stdin, stdout, stderr와 함께 보면 프로그램 입출력의 기본 골격이 보여요.
  • 실제로 파일을 여는 fopen과 닫는 fclose 문서를 이어서 보면 스트림의 생성과 소멸을 이해할 수 있어요.
  • cppreference 원문에서 FILE 타입의 전체 설명을 확인할 수 있어요.