Menu

C 언어 파일 입출력: fopen, fclose, 그리고 파일 한 줄씩 읽기

C에서 파일을 읽고 쓰는 방법 - fopen 모드 표, NULL 검사, fprintf와 fputc로 쓰기, fgets·fscanf·fgetc로 읽기, 정석적인 한 줄씩 읽기 반복문, 그리고 바이너리 모드를 쓸 때.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

프로그램이 계산한 모든 것은 적어 두지 않으면 종료와 함께 사라집니다. C의 파일 인터페이스는 printf와 같은 헤더인 <stdio.h>에 있으며, 의도적으로 닮아 있습니다. 파일은 바이트의 스트림이고, 화면에서 동작하는 함수들에는 스트림 인자를 받는 쌍둥이가 있습니다.

핸들 타입은 FILE *입니다. FILE 안을 들여다보는 일은 없습니다. 불투명한 타입(typedef 참고)이고, 항상 그것을 가리키는 포인터만 들고 있습니다.

세 단계

모든 파일 작업은 같은 모양을 따릅니다. 열고, 쓰고, 닫습니다.

NULL 검사는 선택적인 편집증이 아닙니다. fopen은 (읽기 모드에서) 파일이 없을 때, 디렉터리에 쓸 수 없을 때, 경로가 틀렸을 때, 프로세스의 파일 핸들이 바닥났을 때 실패하며, 그 모두가 NULL을 반환합니다. NULL 핸들을 쓰는 것은 세그멘테이션 폴트입니다.

오류를 stdout이 아니라 stderr로 보내는 것은 관례입니다. 버퍼링되지 않고, 진짜 출력과 따로 리다이렉트할 수 있습니다.

fopen 모드

두 번째 인자는 짧은 모드 문자열입니다. "w"가 기존 파일을 조용히 비워 버리기 때문에, 이것을 잘못 쓰는 것이 이 영역 전체에서 가장 파괴적인 실수입니다.

모드읽기쓰기파일이 있을 때없을 때
"r"안 됨처음에서 엶실패, NULL 반환
"w"안 됨비워짐생성됨
"a"안 됨끝에만 씀생성됨
"r+"처음에서 엶실패, NULL 반환
"w+"비워짐생성됨
"a+"어디서나 읽고, 끝에 씀생성됨

여기에 b를 덧붙이면("rb", "wb", "ab+") 바이너리 모드입니다. 자세한 건 마지막에 다룹니다.

실제 데이터 손실을 막아 주는 두 가지 규칙입니다.

  • 읽으려면 "r"를 쓰세요. 오타 난 파일 이름에 "r+"는 안전하게 실패하고, "w+"는 빈 파일을 만들 뿐이라 잃은 건 시간뿐입니다. 하지만 "r"를 뜻했는데 올바른 파일 이름에 "w"를 쓰면 데이터가 파괴됩니다.
  • 로그에는 "a"를 쓰세요. 스트림이 어디에 위치해 있든 모든 fprintf가 끝에 떨어지며, 그것이 바로 로그가 원하는 바입니다.

한 프로그램에서 쓰고 읽기

아래 에디터는 완전한 왕복을 실행합니다. 파일을 만들고, 레코드를 쓰고, 닫고, 읽기용으로 다시 열어 찾은 것을 출력합니다.

fprintffscanf는 첫 인자로 스트림을 받는 printfscanf입니다. 형식 지정자에 관한 모든 것이 동일하며, name이 넘치지 않게 막아 주는 %31s 너비도 마찬가지입니다.

반복문 조건은 형식이 요구하는 항목 수인 == 2입니다. 대신 EOF와 비교하는 것은 고전적인 버그입니다. 잘못된 형식의 줄은 fscanfEOF가 아니라 0을 반환하게 만들고, 반복문은 소비하지 못하는 입력 위에서 영원히 돕니다.

fgets로 한 줄씩 읽기

fscanf는 엄격하게 형식이 정해진 데이터에 편리합니다. 텍스트 파일 - 설정, 로그, CSV, 사람이 쓴 무엇이든 - 에는 줄 단위로 읽으세요. 다음 반복문이 정석입니다.

fgets를 올바른 기본값으로 만드는 점들입니다.

  • 버퍼 크기를 받으므로 넘칠 수 없습니다. sizeof line을 넘기면 배열 크기를 바꿔도 호출이 계속 옳습니다.
  • 파일 끝이나 오류에서 NULL을 반환하므로 깔끔한 반복문 조건이 됩니다.
  • 줄이 버퍼에 들어갔다면 줄바꿈을 그대로 둡니다. 이건 유용합니다. 받은 내용에 '\n'이 없다는 것은 줄이 버퍼보다 길었고 나머지가 아직 기다리고 있다는 뜻입니다. strcspn(line, "\n")은 줄바꿈의 인덱스(없으면 문자열 길이)를 찾아 주므로, 거기에 '\0'을 넣으면 어느 쪽이든 잘립니다.

진짜 파일 끝과 오류를 구별하려면 반복문 뒤에 물어보세요.

if (ferror(in)) {
    fprintf(stderr, "read error\n");
} else if (feof(in)) {
    /* 정상 종료 */
}

반복문 조건으로 while (!feof(fp))를 쓰지 마세요. feof는 읽기가 이미 실패한 뒤에야 참이 되므로, 그 반복문은 마지막 버퍼 내용을 한 번 더 처리합니다. 대신 위의 두 반복문처럼 읽기 함수의 반환값을 검사하세요.

한 글자씩: fgetc와 fputc

바이트 수준 작업 - 문자 세기, 파일 변환, 복사 - 에는 호출마다 문자 하나를 다루는 fgetcfputc가 있습니다.

중요한 세부 하나. cchar가 아니라 int로 선언되어 있습니다. fgetcint를 반환하는 것은 가능한 모든 바이트 값 그리고 구별되는 보초 EOF(-1)를 반환할 수 있어야 하기 때문입니다. 이를 char에 저장하면 플랫폼에서 char가 부호 있는지에 따라 EOF 비교가 항상 거짓이 되거나 바이트 0xFF에서 거짓으로 참이 됩니다. C의 가장 오래된 함정 중 하나입니다.

오류를 제대로 검사하기

실전용 읽기는 이렇게 생겼습니다.

errno는 마지막 실패를 설명하는 코드를 담고 strerror는 그것을 문장으로 바꿉니다. perror는 여러분의 메시지와 그 문장을 한 번의 호출로 출력합니다. 각각 <errno.h><string.h>가 필요합니다.

fclose도 실패할 수 있습니다. 버퍼에 있던 데이터를 비우는데 그 쓰기가 디스크에 들어가지 못할 수 있기 때문입니다. 그러니 중요한 것이라면 검사하세요.

if (fclose(fp) != 0) {
    fprintf(stderr, "failed to flush and close\n");
}

바이너리 모드, 한 문단으로

텍스트 모드는 줄 끝을 변환할 수 있고(윈도우에서는 쓸 때 \n\r\n이 되고 읽을 때 되돌아옵니다) 특정 바이트를 특별하게 다룰 수 있습니다. 텍스트가 아닌 데이터 - 이미지, 그대로 덤프한 구조체, 압축된 덩어리 - 는 b를 붙여 열고 원시 바이트를 옮기는 fread/fwrite를 쓰세요.

fwrite(ptr, size, count, fp)size 바이트짜리 항목 count개를 쓰고 실제로 쓴 개수를 반환하며, fread도 같은 모양입니다. 이렇게 쓴 파일은 쓴 기계에 묶인다는 점에 유의하세요. 구조체 패딩, 정수 크기, 바이트 순서가 모두 바이트에 새어 들어갑니다. 캐시나 임시 파일로는 괜찮지만, 다른 프로그램이 읽어야 하는 형식으로는 틀린 선택입니다.

흔한 실수

  • fopenNULL을 검사하지 않기. 뒤따르는 크래시는 열기가 아니라 읽기 탓으로 오해받습니다.
  • "r"를 뜻했는데 "w"로 열기. 알아채기 전에 파일이 비워집니다.
  • fclose를 잊기. 버퍼링된 출력이 사라지고, 닫지 않은 파일마다 핸들이 샙니다.
  • while (!feof(fp)). 마지막 줄을 두 번 처리합니다. 대신 읽기 호출을 검사하세요.
  • char c = fgetc(fp). EOF 비교가 깨집니다. int를 쓰세요.
  • 너비 없는 fscanf("%s", buf). 키보드에서의 scanf와 똑같은 버퍼 오버플로입니다.
  • 상대 경로. fopen("data.txt", "r")는 실행 파일 옆이 아니라 작업 디렉터리를 봅니다. 파일이 "사라졌다"면 보통 그 이유입니다.

자주 묻는 질문

C에서 파일은 어떻게 여나요?

FILE *fp = fopen("data.txt", "r");는 읽기용으로 파일을 열고 FILE * 핸들을 반환하며, 실패하면 NULL을 반환합니다. 핸들을 쓰기 전에 항상 NULL인지 확인하고, 다 쓰면 fclose(fp)를 호출하세요.

C의 fopen 모드에는 어떤 것이 있나요?

"r"는 읽기(파일이 존재해야 함), "w"는 쓰기(생성하거나 기존 파일을 비웁니다), "a"는 이어쓰기(생성하고 끝에 씁니다)입니다. +를 붙이면 각각 읽기·쓰기가 됩니다. "r+", "w+", "a+"처럼요. b를 붙이면("rb", "wb") 바이너리 모드로 엽니다.

C에서 파일을 한 줄씩 어떻게 읽나요?

while 반복문에서 fgets를 쓰세요. while (fgets(line, sizeof line, fp) != NULL) { ... }처럼요. 줄바꿈마다, 또는 버퍼가 가득 차면 멈추고, 파일 끝에서 NULL을 반환하며, 버퍼 크기를 넘기므로 넘칠 수 없습니다.

C에서 파일에 썼는데 왜 비어 있나요?

대개 fclose를 잊은 것입니다. 출력은 버퍼링되므로 프로그램이 비정상적으로 끝나면 데이터가 아직 메모리에 남아 있을 수 있습니다. fclose는 비우고 닫으며, fflush(fp)는 닫지 않고 비웁니다. 다른 원인은 "w"로 두 번째로 여는 것인데, 방금 쓴 파일을 잘라냅니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기