Quick Reference
파일은 열기·읽기 또는 쓰기·닫기 순서로 다룹니다. 반복 조건은 feof가 아니라 실제 읽기 함수의 반환값으로 둡니다.
FILE *fp = fopen("notes.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
char line[256];
while (fgets(line, sizeof line, fp) != NULL) {
/* 줄 하나를 정상적으로 읽은 경우만 처리 */
}
if (ferror(fp)) {
/* EOF가 아닌 읽기 오류 */
}
if (fclose(fp) != 0) {
/* buffered write의 실패가 여기서 드러날 수 있다. */
}텍스트는 "r"·"w", 바이너리는 "rb"·"wb"를 명시합니다. feof는 읽기가 실패한 뒤의 상태이므로 반복 조건으로 쓰지 않고, 읽기 함수의 반환값으로 성공을 판단합니다.
문법
파일 I/O는 함수 이름을 많이 아는 것보다 열기 성공 여부, 읽기/쓰기 반환값, 닫기 책임을 놓치지 않는 쪽이 훨씬 중요합니다.
"r": 읽기"w": 새로 쓰기, 기존 내용 덮어씀"a": 뒤에 이어쓰기
그리고 바이너리면 b를 붙입니다.
"rb""wb"
텍스트와 바이너리를 섞어 생각하지 않는 것이 중요합니다.
텍스트 파일은 줄 단위가 기본 패턴에 가깝습니다.
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}- 버퍼 크기를 넘지 않는다
- 개행 포함 여부를 눈으로 확인할 수 있다
- 루프 조건이 읽기 성공 여부와 바로 연결된다
while (!feof(fp)) 같은 패턴보다 훨씬 안전합니다.
루프가 끝난 뒤 ferror(fp)가 참이면 EOF가 아니라 읽기 오류가 난 것입니다. 줄이 버퍼에 다 들어가지 않았을 때는 개행이 남지 않을 수 있으므로, 긴 줄을 허용할지 오류로 볼지도 정합니다.
확인 포인트
size_t nread = fread(buf, sizeof(buf[0]), count, fp);
if (nread < count && ferror(fp)) {
perror("fread");
}바이너리는 "원소 수" 기준으로 읽고 씁니다.
- 두 번째 인자: 원소 하나 크기
- 세 번째 인자: 원소 개수
- 반환값: 실제로 읽거나 쓴 원소 수
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
rewind(fp);이 패턴은 파일 크기를 확인하거나 다시 처음부터 읽을 때 자주 씁니다.
다만 ftell 결과를 무조건 "유효한 전체 파일 크기"로 가정하기보다, 스트림 종류와 실패 여부를 같이 봐야 합니다.
r+, w+, a+처럼 update mode를 쓰면 읽기와 쓰기 사이에 fflush, fseek, fsetpos, rewind 같은 위치 전환이 필요합니다. 또한 fclose는 쓰기 버퍼를 실제로 내보내며 실패를 반환할 수 있으므로, 저장 성공이 중요하면 반환값을 확인합니다. 구조체를 fwrite로 그대로 저장하는 것은 padding·endianness·포인터 때문에 portable serialization이 아닙니다.
빠른 점검
fopen실패를 검사하지 않음fclose를 빼먹음fread반환값을 무시함while (!feof(fp))로 루프를 제어함- 바이너리 파일을 텍스트 모드로 엶
주의할 점
feof(fp)는 "이제 끝이다"가 아니라 "끝을 넘겨 읽으려 시도했다"는 신호입니다. 그래서 while (!feof(fp))로 루프를 돌리면 마지막 처리에서 한 번 더 엇나가기 쉽습니다. 읽기 함수의 반환값으로 루프를 제어하는 편이 맞습니다.
참고 링크
2 sources