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

C언어 - 파일 입출력

by kik328288 2026. 5. 5.

C언어 파일 입출력 완벽 정리, 실기 예제 포함

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

 

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

C언어에서 파일을 다루기 위한 출발점은 FILE 구조체의 포인터를 얻는 일이다. stdio.h 헤더에 정의된 FILE은 운영체제가 파일에 접근하기 위해 필요한 정보, 즉 파일 디스크립터와 버퍼, 현재 위치 등을 담고 있는 자료형이다. 사용자는 이 구조체의 내부를 직접 다룰 필요가 없으며, 표준 라이브러리 함수에 FILE 포인터를 넘겨 간접적으로 조작한다. 파일을 열기 위해서는 fopen 함수를 사용하고, 사용이 끝난 후에는 반드시 fclose를 호출해 자원을 반환해야 한다.

fopen은 두 개의 문자열 인자를 받는다. 첫 번째는 파일의 경로이고, 두 번째는 어떤 방식으로 파일을 열지 결정하는 모드 문자열이다. 모드는 크게 읽기·쓰기·추가의 세 가지 기본 동작과, 텍스트·바이너리의 두 가지 형식이 결합되어 결정된다. 자주 사용되는 모드는 다음과 같다.

모드 의미
"r" 읽기 전용. 파일이 없으면 NULL 반환
"w" 쓰기 전용. 파일이 있으면 기존 내용을 모두 지우고 새로 씀
"a" 추가 모드. 파일 끝에 이어서 씀
"r+" 읽기·쓰기 둘 다 가능
"rb", "wb", "ab" 바이너리 모드 (이미지·실행파일 등)

fopen이 성공하면 유효한 FILE 포인터를 반환하고, 실패하면 NULL을 반환한다. 따라서 동적 메모리 할당과 마찬가지로 NULL 검사를 거치는 것이 표준이며, 검사 없이 사용하면 세그멘테이션 오류로 프로그램이 비정상 종료될 수 있다.

#include <stdio.h>

int main() {
    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) 동작까지 수행한다. 따라서 fclose를 호출하지 않으면 작성한 내용이 파일에 반영되지 않고 사라질 수 있다. 한 프로그램에서 동시에 열 수 있는 파일의 수는 시스템마다 제한이 있으므로, 사용을 끝낸 파일은 즉시 닫는 습관이 중요하다.

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

파일에 데이터를 기록하고 읽어 들이는 함수는 입출력 형식에 따라 두 계열로 나뉜다. 사람이 읽을 수 있는 형태로 처리하는 텍스트 입출력 함수와, 메모리 비트를 그대로 옮기는 바이너리 입출력 함수이다. 텍스트 입출력의 대표 함수는 fprintf와 fscanf이며, 우리가 익숙한 printf·scanf와 형식이 거의 동일하지만 첫 번째 인자로 FILE 포인터를 받는다는 점만 다르다.

// 쓰기
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, 문자열 단위의 fputs도 자주 사용된다. 특히 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 포인터이다. 함수가 실제로 처리한 원소 개수를 반환하므로, 이 값을 검사해 부분 읽기·쓰기를 감지할 수 있다.

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

정보처리기사 실기에서 파일 입출력은 fopen 모드의 차이와 fprintf·fscanf의 출력 결과를 추적하는 형태로 출제된다. 다음 세 가지 패턴이 핵심이다.

유형 1: 쓰기 모드와 추가 모드의 차이.

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로 닫는 세 단계로 요약된다. 모드 문자열의 의미를 헷갈리지 않고, 텍스트와 바이너리의 차이를 분명히 구분하며, 항상 NULL 검사와 fclose 호출을 잊지 않는 습관만 들이면 안전한 파일 처리 코드를 작성할 수 있다. 이 위에서 다음 글의 전처리기와 매크로, 그리고 자료구조의 영속화 작업으로 자연스럽게 이어진다.


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


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

© 2026 블로그 이름