1. Introduction
프로그래밍에서 파일을 읽고 쓰는 것은 가장 중요한 작업 중 하나입니다. C 언어에서는 파일을 여는 방법, 데이터를 쓰는 방법, 파일을 닫는 방법 등 파일 작업의 기본을 이해하는 것이 필수적입니다. 이 글에서는 C에서 파일에 쓰는 기본적인 방법과 실용적인 예제를 설명하는 데 초점을 맞춥니다.
파일에 쓰는 기술은 데이터 영속성과 프로그램 간 데이터 공유를 가능하게 하기 때문에 매우 중요합니다. C에서 파일 작업을 배우면 다른 프로그래밍 언어의 파일 처리 방식을 이해하는 데도 도움이 됩니다. 이 글을 통해 기본 쓰기 작업부터 고급 오류 처리까지 모두 배울 수 있으며, 파일 작업에 대한 이해를 한층 깊게 할 수 있습니다.
다음 섹션에서는 파일을 여는 방법과 닫는 방법, 그리고 다양한 쓰기 모드에 대해 다룰 것입니다.
2. Basics of File Writing
C에서 파일에 쓰려면 먼저 파일을 열어야 합니다. 파일을 열 때는 읽기, 쓰기, 추가 등 목적을 지정해야 합니다. C에서는 fopen 함수를 사용해 파일을 열고 fclose 함수를 사용해 파일을 닫습니다. 이 섹션에서는 기본적인 열기/닫기 작업과 쓰기 모드에 대해 설명합니다.
How to Use the fopen Function
fopen 함수를 사용해 파일을 엽니다. 이 함수는 두 개의 인자를 받습니다: 파일 이름과 모드(파일 작업 유형). fopen의 기본 구문은 다음과 같습니다:
FILE *fopen(const char *filename, const char *mode);
filename: 열고자 하는 파일의 이름(경로)입니다.mode: 파일을 여는 모드(쓰기, 읽기, 추가 등)입니다.
Types of Write Modes
파일을 열 때 사용할 수 있는 여러 모드가 있습니다. 여기서는 쓰기와 가장 관련이 깊은 모드들을 소개합니다:
"w": 쓰기 전용 모드. 파일이 이미 존재하면 내용이 모두 삭제됩니다. 파일이 없으면 새 파일이 생성됩니다."a": 추가 모드. 파일이 존재하면 파일 끝에 데이터를 추가하고, 존재하지 않으면 새 파일이 생성됩니다."wb": 바이너리 쓰기 모드."w"와 비슷하지만 텍스트 인코딩 변환 없이 바이너리 형식으로 씁니다.
Example of Writing to a File
아래 예제는 새 파일을 생성하고 "w" 모드로 쓰는 과정을 보여줍니다. 파일이 이미 존재하면 내용이 삭제됩니다.
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w"); // Open file in write mode
if (file == NULL) {
printf("Failed to open file.n");
return 1;
}
fprintf(file, "Hello, this is file writing in C!n"); // Write to file
fclose(file); // Close the file
printf("File writing completed.n");
return 0;
}
이 예제에서 fopen 함수는 “example.txt”를 만들고, fprintf는 텍스트 데이터를 파일에 씁니다. 쓰기가 끝난 후에는 반드시 fclose로 파일을 닫아야 합니다. 그렇지 않으면 데이터가 제대로 저장되지 않을 수 있습니다.
Importance of the fclose Function
파일을 연 후에는 반드시 fclose 함수를 호출해야 합니다. 파일을 닫는 것은 시스템 자원을 해제하고 데이터가 올바르게 저장되도록 보장합니다. 프로그램이 파일을 닫지 않은 채 종료되면 쓰기 작업이 중단될 수 있습니다. 파일 작업을 마친 뒤에는 항상 fclose를 사용하는 습관을 들이세요.
다음 섹션에서는 텍스트 파일에 쓰는 방법을 자세히 살펴보겠습니다.
3. Writing to a Text File
C에서 텍스트 파일에 쓰는 일반적인 방법은 세 가지가 있습니다: 문자 단위, 문자열 단위, 포맷된 데이터 출력. 각각 전용 함수가 있으며 필요에 따라 선택할 수 있습니다. 이 섹션에서는 fputc, fputs, fprintf를 이용한 쓰기에 대해 다룹니다.
Writing a Single Character with fputc
fputc 함수는 파일에 한 번에 한 문자씩 씁니다. 간단하면서도 개별 문자를 직접 써야 할 때 유용합니다. 구문은 다음과 같습니다:
int fputc(int character, FILE *stream);
character: 쓰고자 하는 문자stream: 파일 포인터
Example: Using fputc
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
printf("Failed to open file.n");
return 1;
}
fputc('A', file); // Write 'A'
fputc('B', file); // Write 'B'
fputc('n', file); // Write newline
fclose(file);
printf("Character writing completed.n");
return 0;
}
여기서는 문자 'A'와 'B'를 파일에 각각 개별적으로 기록합니다. 이 방법은 소규모 데이터 출력에 편리합니다.
fputs를 사용하여 문자열 쓰기
fputs 함수는 전체 문자열을 한 번에 씁니다. 각 문자를 일일이 기록할 필요가 없으므로 텍스트 출력에 효율적입니다. 구문:
int fputs(const char *str, FILE *stream);
str: 쓸 문자열stream: 파일 포인터
예시: fputs 사용
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
printf("Failed to open file.n");
return 1;
}
fputs("This is an example of writing with fputs.n", file);
fclose(file);
printf("String writing completed.n");
return 0;
}
여기서는 fputs가 전체 문장을 한 번에 파일에 씁니다. 텍스트를 빠르고 효율적으로 출력하는 데 이상적입니다.
fprintf를 사용하여 형식화된 데이터 쓰기
fprintf 함수는 printf의 파일 버전과 같으며, 형식화된 데이터를 출력할 수 있게 해줍니다. 숫자와 문자열을 특정 형식으로 혼합하여 기록할 때 유용합니다.
int fprintf(FILE *stream, const char *format, ...);
stream: 파일 포인터format: 형식 지정자가 포함된 문자열
예시: fprintf 사용
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
printf("Failed to open file.n");
return 1;
}
int number = 123;
float decimal = 45.67;
fprintf(file, "Integer: %d, Float: %.2fn", number, decimal);
fclose(file);
printf("Formatted data writing completed.n");
return 0;
}
이 예시에서는 정수와 부동소수점 숫자를 형식화하여 기록합니다. %d와 %.2f와 같은 형식 지정자를 사용하면 출력 형태를 정확히 제어할 수 있습니다.
요약
fputc, fputs, fprintf는 C에서 텍스트 파일에 쓰기 위한 강력한 함수들입니다. 필요에 맞는 함수를 선택하면 데이터를 효율적이고 유연하게 기록할 수 있습니다. 다음 섹션에서는 바이너리 파일 쓰기에 대해 다룹니다.
4. 바이너리 파일에 쓰는 방법
C에서는 텍스트 파일뿐만 아니라 바이너리 파일에도 쓸 수 있습니다. 이미지, 오디오 또는 구조체의 원시 데이터를 저장해야 할 때 바이너리 파일에 쓰는 것이 유용합니다. 이 섹션에서는 바이너리 데이터 출력을 위한 fwrite 함수 사용 방법을 설명하고, 바이너리 파일을 다룰 때 유의해야 할 핵심 사항을 강조합니다.
fwrite 함수 사용
fwrite 함수는 지정된 메모리 위치의 데이터를 파일에 직접 기록합니다. 원시 바이트를 저장하기 때문에 문자열뿐만 아니라 복잡한 데이터 구조도 처리할 수 있습니다.
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
ptr: 기록할 데이터에 대한 포인터size: 각 요소의 크기(바이트 단위)count: 기록할 요소 개수stream: 파일 포인터
바이너리 모드로 파일 열기
바이너리 파일을 쓸 때는 "wb" 또는 "ab"와 같은 모드를 사용합니다. 이렇게 하면 텍스트 모드에서 발생하는 개행이나 문자 변환 없이 메모리 그대로 데이터를 저장할 수 있습니다.
예시: fwrite를 사용한 바이너리 데이터 쓰기
#include <stdio.h>
int main() {
FILE *file = fopen("example.bin", "wb");
if (file == NULL) {
printf("Failed to open file.n");
return 1;
}
int data[] = {10, 20, 30, 40, 50};
size_t dataSize = sizeof(data) / sizeof(data[0]);
fwrite(data, sizeof(int), dataSize, file);
fclose(file);
printf("Binary data writing completed.n");
return 0;
}
이 예제에서는 fwrite를 사용하여 정수 배열을 바이너리 파일에 기록합니다. 데이터가 원시 형태로 저장되기 때문에 대용량 데이터셋에서도 효율적으로 기록할 수 있습니다.
바이너리 파일 사용 시 주의할 점
- 데이터 호환성 : 바이너리 데이터는 시스템에 따라 달라질 수 있어 다른 컴퓨터에서 다르게 해석될 수 있습니다. 동일한 시스템에서 읽고 쓸 때는 보통 문제가 없지만, 플랫폼 간에 공유할 경우 주의가 필요합니다.
- 엔디언(바이트 순서) : 소스 시스템과 대상 시스템의 바이트 순서(엔디언)가 다르면 바이너리 데이터를 잘못 읽을 수 있습니다. 플랫폼 간 데이터 공유를 위해서는 엔디언 변환이 필요합니다.
- 줄바꿈 처리 : 바이너리 모드에서는 줄바꿈 문자 및 기타 제어 문자가 변환 없이 그대로 저장되어 데이터가 정확히 보존됩니다.
요약
원시 데이터를 저장해야 할 때 바이너리 파일에 쓰는 것이 효과적입니다. fwrite를 사용하면 복잡한 데이터 타입도 효율적이고 유연하게 저장할 수 있습니다. 다음 섹션에서는 파일 작업에서의 오류 처리에 대해 다루겠습니다.

5. 오류 처리
파일 작업을 수행할 때는 파일이 없거나 접근 권한이 부족한 경우와 같이 오류가 발생할 수 있습니다. 이러한 오류를 올바르게 처리하면 예기치 않은 동작을 방지하고 프로그램의 신뢰성을 높일 수 있습니다. 이 섹션에서는 C에서 파일 작업에 대한 오류 처리 방법을 설명합니다.
파일 열기 시 오류 확인
파일이 존재하지 않거나 프로그램에 필요한 권한이 없으면 fopen이 NULL을 반환합니다. 이를 확인함으로써 오류를 우아하게 처리할 수 있습니다.
예시: 파일 열기 오류 확인
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
// File operations go here
fclose(file);
return 0;
}
여기서는 fopen이 실패했을 때 perror 함수가 실패 이유를 포함한 오류 메시지를 출력합니다.
perror와 strerror를 사용한 오류 메시지 표시
C는 오류 메시지를 표시하기 위한 편리한 함수로 perror와 strerror를 제공합니다.
perror: 사용자 정의 메시지와 함께 시스템 오류 설명을stderr에 출력합니다.strerror: 전달된 오류 코드에 대한 설명 문자열을 반환합니다.
예시: strerror 사용
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
printf("Error: %sn", strerror(errno));
return 1;
}
fclose(file);
return 0;
}
이 예제에서는 errno 변수를 strerror에 전달하여 오류에 대한 사람이 읽을 수 있는 설명을 얻습니다.
쓰기 오류 감지 및 처리
파일 작업 중에도 쓰기 오류가 발생할 수 있습니다. ferror 함수는 이러한 오류를 검사하고 오류가 발생했을 경우 0이 아닌 값을 반환합니다.
예시: 쓰기 오류 감지
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
if (fprintf(file, "Writing data") < 0) {
perror("Write error occurred");
fclose(file);
return 1;
}
fclose(file);
printf("Writing completed.n");
return 0;
}
여기서는 fprintf가 음수 값을 반환하면 쓰기 오류가 발생한 것으로 간주하고 오류 메시지를 출력합니다.
요약
오류 처리는 신뢰할 수 있는 프로그램을 작성하는 데 필수적입니다. 파일을 열거나 쓰는 동안 오류를 확인하고 응답함으로써 코드를 더 안전하고 견고하게 만들 수 있습니다. 다음 섹션에서는 로그 파일 쓰기와 구성 파일 생성과 같은 실용적인 응용을 소개합니다.
6. 실용적인 예제
파일 쓰기의 기본을 이해했으므로, 이제 몇 가지 실용적인 응용을 살펴보겠습니다. 실제 프로그래밍에서 파일 작업은 로그 파일 쓰기, 구성 파일 생성, 데이터 직렬화/역직렬화(데이터 구조 저장 및 로드)와 같은 작업에 자주 사용됩니다. 이러한 예제는 파일 작업이 실제 프로젝트에 어떻게 적용될 수 있는지 보여줍니다.
로그 파일 쓰기
로그 파일은 프로그램 활동을 기록하는 데 일반적으로 사용됩니다. 오류 메시지나 프로세스 정보를 로그 파일에 쓰면 문제 해결이 더 쉬워집니다.
예제: 로그 파일 쓰기
#include <stdio.h>
#include <time.h>
void log_message(const char *message) {
FILE *file = fopen("log.txt", "a");
if (file == NULL) {
perror("Failed to open log file");
return;
}
time_t now = time(NULL);
struct tm *t = localtime(&now);
fprintf(file, "[%04d-%02d-%02d %02d:%02d:%02d] %sn",
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, message);
fclose(file);
}
int main() {
log_message("Program started.");
log_message("An error occurred.");
return 0;
}
이 프로그램은 log_message 함수를 사용하여 메시지를 log.txt에 씁니다. 파일은 추가 모드("a")로 열리므로 이전 로그를 삭제하지 않고 새로운 메시지가 추가됩니다. 타임스탬프를 포함하면 각 로그 항목이 기록된 시간을 쉽게 확인할 수 있습니다.
구성 파일 생성
구성 파일은 프로그램의 설정, 예를 들어 초기 매개변수나 사용자 선호도를 저장합니다. 프로그램이 시작될 때 이러한 설정을 적용하기 위해 읽을 수 있습니다.
예제: 구성 파일에 설정 저장
#include <stdio.h>
void save_settings(const char *filename, int volume, int brightness) {
FILE *file = fopen(filename, "w");
if (file == NULL) {
perror("Failed to open configuration file");
return;
}
fprintf(file, "volume=%dn", volume);
fprintf(file, "brightness=%dn", brightness);
fclose(file);
}
int main() {
save_settings("settings.conf", 75, 50);
printf("Configuration file saved.n");
return 0;
}
여기서 save_settings 함수는 볼륨과 밝기 설정을 간단한 key=value 형식으로 settings.conf에 저장합니다. 이는 읽기와 편집이 모두 쉽습니다.
데이터 직렬화 및 역직렬화
직렬화는 데이터 구조를 파일에 직접 저장하여 나중에 로드할 수 있게 하는 프로세스입니다. 이는 게임 진행 상황 저장이나 복잡한 프로그램 데이터 저장에 자주 사용됩니다.
예제: 구조체 직렬화 및 역직렬화
#include <stdio.h>
typedef struct {
int id;
char name[50];
float score;
} Student;
void save_student(const char *filename, Student *student) {
FILE *file = fopen(filename, "wb");
if (file == NULL) {
perror("Failed to open file");
return;
}
fwrite(student, sizeof(Student), 1, file);
fclose(file);
}
void load_student(const char *filename, Student *student) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
perror("Failed to open file");
return;
}
fread(student, sizeof(Student), 1, file);
fclose(file);
}
int main() {
Student s1 = {1, "Taro Sato", 89.5};
save_student("student.dat", &s1);
Student s2;
load_student("student.dat", &s2);
printf("ID: %d, Name: %s, Score: %.2fn", s2.id, s2.name, s2.score);
return 0;
}
In this program, a Student structure is saved to a file in binary format with save_student and later loaded with load_student. The data remains exactly as it was when saved, making it easy to restore later.
요약
이러한 실용적인 예시—로그 파일, 설정 파일, 그리고 직렬화—는 파일 쓰기가 실제 프로그램에서 어떻게 사용되는지를 보여줍니다. 다음에서는 C에서 파일 쓰기에 대한 자주 묻는 질문(FAQ)에 답변합니다.
7. 자주 묻는 질문 (FAQ)
파일을 열 수 없을 때 해야 할 일
Q: fopen이 파일을 열 수 없습니다. 어떻게 해야 하나요?
A: fopen이 NULL을 반환하면 다음을 확인하십시오:
- 파일 경로: 경로가 파일의 실제 위치와 일치하는지 확인합니다.
- 권한: 파일에 대한 읽기/쓰기 권한이 있는지 확인합니다.
- 디스크 공간: 디스크 공간 부족으로 파일 생성이 차단될 수 있습니다.
perror또는strerror사용: 이 함수들은 오류 세부 정보를 표시하여 문제를 파악하는 데 도움을 줍니다.
파일 쓰기가 반영되지 않는 이유는?
Q: 파일에 쓰기를 했지만 변경 사항이 보이지 않습니다.
A: 가능한 원인은 다음과 같습니다:
fclose를 호출하지 않음: 쓰기 후 항상 파일을 닫아 버퍼를 플러시합니다.- 강제 플러시:
fflush(file);을 사용해 버퍼링된 데이터를 즉시 디스크에 기록합니다. - OS 캐싱: 일부 운영 체제는 파일 업데이트를 지연시킬 수 있으니 테스트 시 캐시 동작을 고려하십시오.
바이너리 파일과 텍스트 파일의 차이점
Q: 바이너리 파일은 텍스트 파일과 어떻게 다릅니까?
A: 차이점은 다음과 같습니다:
- 저장 형식: 텍스트 파일은 문자로 데이터를 저장하며 운영 체제에 따라 줄 바꿈 변환이 발생합니다(
"\r\n"은 Windows,"\n"은 Unix/Linux). 바이너리 파일은 변환 없이 원시 바이트를 그대로 저장합니다. - 용도: 텍스트 파일은 사람이 읽을 수 있어 로그나 설정에 사용되고, 바이너리 파일은 구조화된 데이터나 미디어 데이터를 프로그램이 직접 사용하도록 저장합니다.
쓰기 오류 처리
Q: fprintf 또는 fwrite가 실패했습니다. 어떻게 해야 하나요?
A: 다음 절차를 따르세요:
ferror를 사용해 오류 여부를 확인합니다.perror로 상세 정보를 출력합니다.- 저장 공간을 확인합니다.
- 파일이 올바른 모드로 열렸는지 확인합니다.
바이너리 파일의 엔디안 문제
Q: 시스템 간에 바이너리 파일을 공유하는데 엔디안 차이 때문에 오류가 발생합니다.
A: 엔디안은 바이트 순서를 결정합니다. 해결 방법은 다음과 같습니다:
htons,htonl같은 변환 함수를 사용해 일관된 바이트 순서를 유지합니다.- 모든 시스템에서 동일한 엔디안을 사용하도록 표준화하거나 파일에 엔디안 플래그를 저장합니다.
- 파일을 읽을 때 엔디안을 감지하고 적절히 변환하는 코드를 작성합니다.
요약
올바른 오류 처리, 파일 닫기, 그리고 바이너리/텍스트 차이에 대한 이해를 통해 C 파일 작업에서 흔히 발생하는 함정을 피할 수 있습니다. 다음에서는 이 가이드의 핵심 내용을 정리합니다.
8. 결론
이 가이드는 C에서 파일 쓰기를 기본부터 고급 응용까지 다루었습니다. 파일 작업은 영구적인 데이터 저장에 필수적입니다. 주요 포인트를 다시 살펴보겠습니다:
기본
fopen과 fclose를 사용해 파일을 열고 닫는 방법을 배웠으며, "w", "a", "wb"와 같은 모드를 소개했습니다.
텍스트 파일 쓰기
fputc, fputs, fprintf를 이용한 텍스트 쓰기를 다루었으며, 각각 단일 문자, 전체 문자열, 포맷된 출력에 적합합니다.
바이너리 파일 쓰기
fwrite를 사용해 원시 데이터를 저장하는 방법을 살펴보았고, 호환성 및 엔디안 고려 사항을 설명했습니다.
오류 처리
perror, strerror, ferror를 활용해 오류를 감지하고 대응하는 방법을 논의했습니다.
실용적인 적용
로그 파일, 설정 파일, 직렬화/역직렬화와 같은 실제 사용 사례를 시연했습니다.
최종 생각
C에서 파일 쓰기를 마스터하면 C뿐만 아니라 유사한 개념이 적용되는 다른 프로그래밍 언어에서도 실력을 향상시킬 수 있습니다. 이 지식을 활용해 프로젝트에서 다양한 데이터 저장 및 관리 작업을 효율적으로 처리하십시오.


