본문 바로가기
카테고리 없음

C언어 - 파일 입출력

by kik328288 2026. 5. 5.

C언어 파일 (fopen, fread, 실기)

지금까지의 C 프로그램은 모두 메모리 위에서만 동작했다. 즉 프로그램이 종료되면 그동안 처리한 데이터도 함께 사라진다. 그러나 현실의 응용 프로그램은 학생 명단을 파일에 저장하거나, 로그를 텍스트로 기록하거나, 이미지 같은 바이너리 파일을 직접 다루어야 하는 경우가 대부분이다. 이러한 영속적인 데이터 처리를 가능하게 하는 도구가 바로 파일 입출력(File I/O)이다(출처: cppreference — File I/O). 정보처리기사 실기에서도 fopen·fprintf·fscanf의 호출 흐름과 모드 문자열의 차이를 묻는 문제가 자주 출제된다. 본 글은 FILE 구조체의 개념과 핵심 함수, 그리고 시험 빈출 패턴을 코드 예시와 함께 한 번에 정리한다. 제가 학교 프로젝트에서 가장 자주 사고가 난 부분이 fclose를 빠뜨려 파일에 일부 내용이 누락되는 일이었고, "출력 버퍼는 fclose가 호출되어야 비로소 디스크에 반영된다"는 한 줄을 외운 후로는 사고가 거의 사라졌다.

FILE 포인터와 파일 열기·닫기

C언어에서 파일을 다루기 위한 출발점은 FILE 구조체의 포인터를 얻는 일이다. stdio.h 헤더에 정의된 FILE은 운영체제가 파일에 접근하기 위해 필요한 정보, 즉 파일 디스크립터와 버퍼, 현재 위치 등을 담고 있는 자료형이다. 여기서 파일 디스크립터(file descriptor)란 운영체제 커널이 한 프로세스가 연 파일을 식별하기 위해 부여하는 정수 핸들을 가리키며, 0·1·2가 각각 표준 입력·출력·에러로 예약되어 있다. 사용자는 이 구조체의 내부를 직접 다룰 필요가 없으며, 표준 라이브러리 함수에 FILE 포인터를 넘겨 간접적으로 조작한다.

fopen은 두 개의 문자열 인자를 받는다. 첫 번째는 파일의 경로이고, 두 번째는 어떤 방식으로 파일을 열지 결정하는 모드 문자열이다. 자주 사용되는 모드는 다음과 같다.

모드 의미 파일이 없을 때
"r" 읽기 전용 NULL 반환
"w" 쓰기 전용 (기존 내용 삭제) 새로 생성
"a" 추가 모드 (파일 끝에 이어 씀) 새로 생성
"r+" 읽기·쓰기 NULL 반환
"rb"·"wb"·"ab" 바이너리 모드 위와 동일
#include <stdio.h>
int main(void) {
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) { printf("실패\n"); return 1; }

    fprintf(fp, "Hello, File I/O!\n");
    fprintf(fp, "Score: %d\n", 95);

    fclose(fp);   // 반드시 닫기 — 버퍼 플러시
    return 0;
}

fclose는 단순히 파일을 닫는 것뿐만 아니라, 출력 버퍼에 남아 있는 데이터를 디스크에 마저 기록하는 플러시(flush) 동작까지 수행한다. 여기서 플러시란 메모리 버퍼에 임시로 쌓여 있는 데이터를 강제로 디스크에 기록하는 동작을 가리키며, 표준 출력에서도 fflush(stdout)로 같은 효과를 만들 수 있다. 따라서 fclose를 호출하지 않으면 작성한 내용이 파일에 반영되지 않고 사라질 수 있다.

텍스트 입출력과 바이너리 입출력

파일에 데이터를 기록하고 읽어 들이는 함수는 입출력 형식에 따라 두 계열로 나뉜다. 사람이 읽을 수 있는 형태로 처리하는 텍스트 입출력 함수와, 메모리 비트를 그대로 옮기는 바이너리 입출력 함수이다.

// 텍스트 — fprintf / fscanf
FILE *fp = fopen("score.txt", "w");
fprintf(fp, "%s %d\n", "KIM", 90);
fprintf(fp, "%s %d\n", "LEE", 85);
fclose(fp);

fp = fopen("score.txt", "r");
char name[20]; int score;
while (fscanf(fp, "%s %d", name, &score) == 2) {
    printf("%s -> %d\n", name, score);
}
fclose(fp);

fscanf는 정상적으로 읽어 들인 항목의 개수를 반환한다는 사실이 중요하다. 한 줄당 두 항목을 읽으므로, 반환값이 2일 때만 루프를 계속 진행하도록 하면 파일 끝에서 자연스럽게 종료된다. 이외에 줄 단위로 읽는 fgets, 한 글자씩 처리하는 fgetc·fputc도 자주 사용된다. 특히 fgets는 한 번에 한 줄을 통째로 읽어 들이므로 공백이 포함된 문장을 안전하게 처리할 수 있어, 텍스트 파일 분석에서 가장 신뢰성 있는 함수로 평가된다.

// 바이너리 — fwrite / fread (구조체 통째로)
typedef struct { int id; char name[20]; double gpa; } Student;
Student arr[2] = { {1,"KIM",4.5}, {2,"LEE",4.0} };

FILE *fp = fopen("students.bin", "wb");
fwrite(arr, sizeof(Student), 2, fp);
fclose(fp);

Student loaded[2];
fp = fopen("students.bin", "rb");
fread(loaded, sizeof(Student), 2, fp);
fclose(fp);
printf("%s %.1f\n", loaded[0].name, loaded[0].gpa);   // KIM 4.5

fwrite와 fread는 인자를 네 개 받는다. 데이터 시작 주소, 한 원소의 크기, 원소의 개수, FILE 포인터의 순서이며, 함수가 실제로 처리한 원소 개수를 반환하므로 이 값을 검사해 부분 읽기·쓰기를 감지할 수 있다. 솔직히 이건 예상 밖이었는데, 제가 학교 과제에서 텍스트로만 저장하던 학생 데이터를 바이너리로 갈아 끼웠을 때 파일 크기가 1/3로 줄고 읽기 속도가 5배 빨라지는 모습을 본 후로는 두 방식의 트레이드오프를 손끝으로 받아들였다.

정보처리기사 실기 빈출 파일 입출력 예제

정보처리기사 실기에서 파일 입출력은 fopen 모드의 차이와 fprintf·fscanf의 출력 결과를 추적하는 형태로 출제된다(출처: Q-Net 정보처리기사).

유형 1 — 쓰기 모드("w") vs 추가 모드("a").

FILE *fp = fopen("log.txt", "w");
fprintf(fp, "first\n"); fclose(fp);

fp = fopen("log.txt", "w");          // "w"로 다시 열면 기존 내용 삭제
fprintf(fp, "second\n"); fclose(fp);
// 결과 파일 내용: second   (first는 사라짐)

"w"는 파일을 처음부터 새로 쓰기 때문에 기존 데이터가 사라진다. 만약 기존 내용을 보존하고 이어 쓰려면 "a" 모드를 사용해야 한다는 사실이 출제 포인트이다.

유형 2 — 줄 단위 읽기와 fgets.

FILE *fp = fopen("input.txt", "r");
char line[100];
while (fgets(line, sizeof(line), fp) != NULL) {
    printf("%s", line);
}
fclose(fp);

fgets는 두 번째 인자로 받은 크기에서 1을 뺀 만큼 또는 개행 문자를 만날 때까지 읽어 들이며, 읽어 들인 개행 문자도 문자열에 포함된다는 특성이 자주 출제된다.

유형 3 — 바이너리 입출력과 sizeof.

int data[5] = {10, 20, 30, 40, 50};
FILE *fp = fopen("nums.bin", "wb");
fwrite(data, sizeof(int), 5, fp);
fclose(fp);

int loaded[5];
fp = fopen("nums.bin", "rb");
fread(loaded, sizeof(int), 5, fp);
fclose(fp);
printf("%d %d\n", loaded[0], loaded[4]);   // 10 50

fwrite와 fread가 sizeof와 결합되는 표준 패턴이다. 인자의 순서를 정확히 외우고, 두 호출이 짝을 이뤄야 한다는 점을 기억해야 한다. 결국 파일 입출력은 fopen으로 열고, 적절한 함수로 읽거나 쓰고, fclose로 닫는 세 단계로 요약된다. 솔직히 제 경험상 학교 첫 학기에 가장 자주 빠뜨린 게 fclose 호출이었고, 그 한 줄의 누락으로 파일 끝부분이 잘려나가는 사고를 한 번 겪고 나서야 "출력 버퍼는 fclose가 호출되어야 비로소 디스크에 반영된다"는 한 줄을 손끝으로 외우게 되었다.


메타 디스크립션: C언어의 파일 입출력 함수 fopen·fclose·fprintf·fscanf·fread·fwrite의 차이와 모드 문자열의 의미, 텍스트와 바이너리 처리 방식, 그리고 정보처리기사 실기 빈출 3패턴을 코드 예시와 함께 정리합니다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 블로그 이름