C언어에서 텍스트 파일 입출력은 데이터를 저장하거나 불러오는 중요한 작업 중 하나입니다. 텍스트 파일을 활용하면 프로그램이 외부 데이터와 상호작용하거나, 처리 결과를 영구적으로 보존할 수 있습니다. 본 기사에서는 fprintf
, fscanf
, fgets
, fputs
등 대표적인 파일 입출력 함수와 그 사용법을 소개하고, 이를 활용한 실용적인 예제를 통해 이해를 돕고자 합니다.
다음 항목으로 진행할까요?
파일 입출력의 기본 개념
C언어에서 파일 입출력은 파일 포인터를 사용하여 수행됩니다. 파일 작업을 위해서는 파일을 열고, 데이터를 읽거나 쓰는 작업을 수행한 뒤 파일을 닫는 것이 기본적인 흐름입니다.
파일 포인터와 fopen 함수
파일 작업은 FILE
구조체를 가리키는 파일 포인터를 통해 이루어집니다. fopen
함수는 파일을 열고 파일 포인터를 반환합니다. 사용법은 다음과 같습니다:
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r"); // 읽기 모드로 파일 열기
if (file == NULL) {
perror("파일 열기 실패");
return 1;
}
// 파일 작업 수행
fclose(file); // 파일 닫기
return 0;
}
fclose 함수
파일을 열었다면 작업이 끝난 후 반드시 fclose
를 호출하여 파일을 닫아야 합니다. 파일을 닫지 않으면 데이터 손실이나 리소스 누수가 발생할 수 있습니다.
파일 작업 흐름 요약
fopen
을 사용하여 파일을 열고 파일 포인터를 얻습니다.- 입출력 작업(
fprintf
,fscanf
, 등)을 수행합니다. fclose
를 호출하여 파일을 닫습니다.
파일 입출력의 기본을 숙지하면 복잡한 파일 작업도 쉽게 이해할 수 있습니다. 다음 항목으로 진행할까요?
fprintf와 fscanf 함수의 활용
fprintf
와 fscanf
는 파일에 형식화된 데이터를 쓰고 읽을 때 사용하는 함수입니다. 이 함수들은 콘솔 입출력 함수인 printf
와 scanf
의 파일 버전이라고 할 수 있습니다.
fprintf 함수
fprintf
함수는 지정된 파일에 형식화된 문자열을 출력합니다. 사용법은 다음과 같습니다:
#include <stdio.h>
int main() {
FILE *file = fopen("data.txt", "w"); // 쓰기 모드로 파일 열기
if (file == NULL) {
perror("파일 열기 실패");
return 1;
}
int age = 25;
float score = 92.5;
fprintf(file, "Age: %d\nScore: %.2f\n", age, score); // 형식화된 데이터 쓰기
fclose(file); // 파일 닫기
return 0;
}
위 코드는 data.txt
파일에 “Age: 25\nScore: 92.50\n”과 같은 형식화된 데이터를 씁니다.
fscanf 함수
fscanf
함수는 파일에서 데이터를 읽어와 지정된 변수에 저장합니다. 사용법은 다음과 같습니다:
#include <stdio.h>
int main() {
FILE *file = fopen("data.txt", "r"); // 읽기 모드로 파일 열기
if (file == NULL) {
perror("파일 열기 실패");
return 1;
}
int age;
float score;
fscanf(file, "Age: %d\nScore: %f\n", &age, &score); // 파일에서 데이터 읽기
printf("Age: %d, Score: %.2f\n", age, score);
fclose(file); // 파일 닫기
return 0;
}
위 코드는 data.txt
에서 데이터를 읽어와 변수 age
와 score
에 저장한 뒤 출력합니다.
유의점
fprintf
와fscanf
는 데이터의 형식이 정확히 맞아야 올바르게 작동합니다.- 파일 작업 실패에 대비하여 항상 파일 포인터의 유효성을 확인해야 합니다.
형식화된 데이터 작업은 fprintf
와 fscanf
를 통해 효율적으로 수행할 수 있습니다. 다음 항목으로 진행할까요?
fgets와 fputs 함수의 활용
fgets
와 fputs
는 텍스트 파일에서 문자열 데이터를 읽고 쓰는 데 사용되는 함수입니다. 이 함수들은 줄 단위 작업에 적합하며, 특히 텍스트 데이터를 다룰 때 유용합니다.
fgets 함수
fgets
는 파일에서 한 줄을 읽어와 지정된 문자열 배열에 저장합니다. 사용법은 다음과 같습니다:
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r"); // 읽기 모드로 파일 열기
if (file == NULL) {
perror("파일 열기 실패");
return 1;
}
char buffer[100];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer); // 파일에서 읽은 줄을 출력
}
fclose(file); // 파일 닫기
return 0;
}
위 코드는 example.txt
파일에서 줄 단위로 데이터를 읽어와 콘솔에 출력합니다.
fputs 함수
fputs
는 문자열 데이터를 파일에 쓰는 데 사용됩니다. 사용법은 다음과 같습니다:
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w"); // 쓰기 모드로 파일 열기
if (file == NULL) {
perror("파일 열기 실패");
return 1;
}
fputs("This is a line of text.\n", file); // 문자열 쓰기
fputs("Another line of text.\n", file);
fclose(file); // 파일 닫기
return 0;
}
위 코드는 example.txt
파일에 두 줄의 문자열을 씁니다.
유의점
fgets
는 줄바꿈 문자(\n
)를 포함하여 문자열을 읽어옵니다. 이를 출력할 때 추가로 줄바꿈을 하지 않도록 주의해야 합니다.fputs
는 자동으로 줄바꿈을 추가하지 않으므로 필요하다면 문자열에\n
을 명시적으로 포함해야 합니다.
fgets와 fputs의 조합
파일에서 데이터를 읽어 일부 수정한 후 다시 쓰는 작업은 fgets
와 fputs
의 조합으로 간단히 구현할 수 있습니다.
텍스트 파일 작업에서 fgets
와 fputs
는 단순성과 효율성을 모두 제공하는 유용한 도구입니다. 다음 항목으로 진행할까요?
텍스트 파일 작업 시 주의사항
텍스트 파일 입출력을 다룰 때는 코드 작성과 실행 과정에서 발생할 수 있는 다양한 오류를 미리 방지하고 적절히 처리하는 것이 중요합니다.
파일 열기 오류 처리
파일이 존재하지 않거나 잘못된 경로를 참조할 경우 fopen
은 NULL
을 반환합니다. 이를 확인하지 않으면 프로그램이 예상치 못한 동작을 할 수 있습니다.
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
perror("파일 열기 실패");
return 1; // 오류 처리 후 프로그램 종료
}
파일 모드 확인
파일을 열 때 사용되는 모드(r
, w
, a
, 등)가 작업의 목적에 맞지 않으면 의도한 대로 동작하지 않습니다. 예를 들어, 읽기(r
) 모드에서 파일이 없으면 fopen
이 실패하고, 쓰기(w
) 모드에서는 기존 파일이 삭제됩니다.
- 읽기 전용:
r
- 쓰기(기존 파일 덮어쓰기):
w
- 추가 쓰기(기존 내용 유지):
a
파일 작업 시 리소스 관리
파일 포인터를 열고 나서 fclose
를 호출하지 않으면 메모리 누수와 시스템 리소스 부족 문제가 발생할 수 있습니다.
FILE *file = fopen("example.txt", "r");
// 파일 작업 수행
fclose(file); // 반드시 닫기
텍스트 형식과 플랫폼 차이
- 윈도우와 유닉스 기반 시스템은 줄바꿈 문자에서 차이가 있습니다. 윈도우는
\r\n
, 유닉스 계열은\n
만 사용합니다. - 이로 인해 크로스플랫폼 환경에서 파일을 처리할 때 불필요한 줄바꿈 문자가 포함될 수 있습니다. 이를 보완하려면 파일 모드를 바이너리 모드(
rb
,wb
)로 설정하거나 환경에 맞는 처리를 추가해야 합니다.
버퍼 크기 초과 방지
fgets
를 사용할 때 읽어들일 문자열의 크기를 초과하면 버퍼 오버플로우가 발생할 수 있습니다. 항상 충분한 크기의 버퍼를 사용하고 크기를 명시적으로 지정해야 합니다.
char buffer[100];
fgets(buffer, sizeof(buffer), file);
오류 메시지 출력과 디버깅
- 오류 발생 시
perror
또는strerror(errno)
를 사용하여 구체적인 오류 메시지를 출력하면 디버깅이 용이합니다.
if (file == NULL) {
perror("파일 열기 실패");
}
텍스트 파일 작업 시 이러한 주의사항을 염두에 두면 안정적이고 예측 가능한 파일 입출력을 구현할 수 있습니다. 다음 항목으로 진행할까요?
파일 작업 관련 모드 이해
파일을 열 때 사용하는 모드(r
, w
, a
등)는 텍스트 파일 작업의 동작 방식을 결정합니다. 올바른 모드를 선택하면 데이터 손실을 방지하고 원하는 결과를 얻을 수 있습니다.
기본 파일 모드
r
(읽기 전용)
기존 파일을 읽기 전용으로 엽니다. 파일이 없으면fopen
은 실패하며NULL
을 반환합니다.
FILE *file = fopen("example.txt", "r");
w
(쓰기 전용)
파일을 쓰기 전용으로 엽니다. 파일이 이미 존재하면 내용이 지워지고 새로 작성됩니다. 파일이 없으면 새로 생성됩니다.
FILE *file = fopen("example.txt", "w");
a
(추가 쓰기)
파일을 추가 쓰기 모드로 엽니다. 기존 파일 내용은 유지되며, 모든 쓰기 작업은 파일의 끝에 추가됩니다. 파일이 없으면 새로 생성됩니다.
FILE *file = fopen("example.txt", "a");
확장 파일 모드
r+
(읽기/쓰기)
파일을 읽기와 쓰기 모두 가능하도록 엽니다. 기존 파일 내용을 유지합니다.
FILE *file = fopen("example.txt", "r+");
w+
(쓰기/읽기)
파일을 쓰기와 읽기 모두 가능하도록 엽니다. 파일이 존재하면 기존 내용을 지우고 새로 작성합니다. 파일이 없으면 새로 생성됩니다.
FILE *file = fopen("example.txt", "w+");
a+
(추가 쓰기/읽기)
파일을 추가 쓰기와 읽기가 가능하도록 엽니다. 기존 파일의 내용을 유지하며 모든 쓰기 작업은 파일 끝에서 이루어집니다. 파일이 없으면 새로 생성됩니다.
FILE *file = fopen("example.txt", "a+");
텍스트 모드와 바이너리 모드
- 텍스트 모드
기본 모드로, 줄바꿈 문자 변환(윈도우:\r\n
↔ 유닉스:\n
)이 자동으로 처리됩니다. - 바이너리 모드
파일 내용을 있는 그대로 처리하며 줄바꿈 문자 변환이 일어나지 않습니다. 바이너리 모드는 모드 뒤에b
를 추가하여 사용합니다.
FILE *file = fopen("example.bin", "rb"); // 바이너리 읽기 모드
모드 선택 시 유의점
- 데이터 손실 방지: 기존 데이터를 유지하려면
w
대신a
또는r+
를 사용하는 것이 좋습니다. - 플랫폼 간 호환성: 텍스트 모드는 운영체제 간 차이를 유발할 수 있으므로, 바이너리 모드를 사용하는 것이 안전합니다.
- 목적에 맞는 모드 선택: 파일 작업의 목적에 맞는 모드를 선택하여 불필요한 데이터 손실이나 오류를 방지합니다.
파일 모드를 올바르게 이해하고 사용하면 텍스트 파일 작업의 효율성과 안정성을 높일 수 있습니다. 다음 항목으로 진행할까요?
응용 예제: 간단한 로그 파일 생성
파일 입출력 함수를 활용하여 프로그램 실행 중 발생하는 이벤트를 기록하는 간단한 로그 파일을 생성해 보겠습니다. 이 예제에서는 fprintf
와 fputs
를 사용하여 형식화된 로그 메시지를 파일에 저장합니다.
프로그램 개요
- 프로그램은 특정 이벤트가 발생할 때마다 로그 파일에 메시지를 기록합니다.
- 파일 모드
a
(추가 쓰기)를 사용하여 이전 로그가 삭제되지 않도록 설정합니다.
코드 구현
#include <stdio.h>
#include <time.h>
void write_log(const char *filename, const char *message) {
FILE *file = fopen(filename, "a"); // 추가 쓰기 모드로 파일 열기
if (file == NULL) {
perror("로그 파일 열기 실패");
return;
}
// 현재 시간 가져오기
time_t now = time(NULL);
struct tm *timeinfo = localtime(&now);
// 형식화된 로그 메시지 작성
fprintf(file, "[%04d-%02d-%02d %02d:%02d:%02d] %s\n",
timeinfo->tm_year + 1900,
timeinfo->tm_mon + 1,
timeinfo->tm_mday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec,
message);
fclose(file); // 파일 닫기
}
int main() {
const char *log_file = "program.log";
// 다양한 이벤트 기록
write_log(log_file, "프로그램 시작");
write_log(log_file, "사용자가 데이터를 입력했습니다.");
write_log(log_file, "프로그램 종료");
printf("로그 파일이 생성되었습니다: %s\n", log_file);
return 0;
}
코드 설명
- 로그 파일 열기
- 파일을 추가 쓰기 모드(
a
)로 열어 이전 로그를 유지하면서 새 로그를 추가합니다.
- 시간 정보 추가
time.h
라이브러리를 사용하여 현재 시간을 가져오고, 로그 메시지에 포함합니다.
- 로그 메시지 작성
fprintf
를 사용하여 시간과 메시지를 형식화하여 파일에 기록합니다.
실행 결과
program.log
파일에 다음과 같은 내용이 기록됩니다:
[2025-01-22 15:30:45] 프로그램 시작
[2025-01-22 15:30:50] 사용자가 데이터를 입력했습니다.
[2025-01-22 15:30:55] 프로그램 종료
응용 포인트
- 프로그램의 디버깅 및 기록 목적으로 활용할 수 있습니다.
- 로그 레벨(예: INFO, WARNING, ERROR)을 추가하여 더 체계적인 로그 시스템을 구현할 수 있습니다.
로그 파일 생성 예제를 통해 파일 입출력 함수의 실용성을 이해할 수 있습니다. 다음 항목으로 진행할까요?
실습: 사용자 입력 데이터를 파일로 저장
사용자 입력 데이터를 파일에 저장한 후 다시 읽어오는 프로그램을 작성해 보겠습니다. 이 예제는 fputs
와 fgets
를 사용하여 문자열 데이터를 처리하는 방법을 보여줍니다.
프로그램 개요
- 사용자로부터 이름과 메시지를 입력받습니다.
- 입력받은 데이터를 텍스트 파일에 저장합니다.
- 저장된 데이터를 파일에서 읽어와 출력합니다.
코드 구현
#include <stdio.h>
#include <stdlib.h>
int main() {
const char *filename = "userdata.txt";
FILE *file;
// 사용자 입력 받기
char name[50];
char message[200];
printf("이름을 입력하세요: ");
fgets(name, sizeof(name), stdin);
printf("메시지를 입력하세요: ");
fgets(message, sizeof(message), stdin);
// 파일에 저장하기
file = fopen(filename, "w");
if (file == NULL) {
perror("파일 열기 실패");
return 1;
}
fputs(name, file);
fputs(message, file);
fclose(file);
printf("입력 데이터가 %s 파일에 저장되었습니다.\n", filename);
// 파일에서 읽어오기
file = fopen(filename, "r");
if (file == NULL) {
perror("파일 열기 실패");
return 1;
}
printf("\n저장된 데이터:\n");
char buffer[256];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}
fclose(file);
return 0;
}
코드 설명
- 사용자 입력
fgets
를 사용하여 이름과 메시지를 입력받습니다.- 문자열 입력의 크기를 제한하여 버퍼 오버플로우를 방지합니다.
- 파일에 데이터 저장
- 파일을 쓰기 모드(
w
)로 열고,fputs
를 사용해 데이터를 저장합니다. - 파일 포인터를 닫아 리소스를 해제합니다.
- 파일에서 데이터 읽기
- 파일을 읽기 모드(
r
)로 열고,fgets
를 사용해 데이터를 줄 단위로 읽어 출력합니다.
실행 결과
입력 예시:
이름을 입력하세요: Alice
메시지를 입력하세요: Hello, this is a test message!
파일 내용 (userdata.txt):
Alice
Hello, this is a test message!
출력:
저장된 데이터:
Alice
Hello, this is a test message!
응용 포인트
- 여러 사용자의 입력 데이터를 파일에 누적 저장하려면 파일을
a
(추가 쓰기) 모드로 열면 됩니다. - 텍스트 데이터의 형식을 지정하여 로그, 설정 파일 등 다양한 작업에 활용할 수 있습니다.
이 실습은 텍스트 파일 입출력의 기본 원리와 활용법을 실질적으로 익힐 수 있도록 도와줍니다. 다음 항목으로 진행할까요?
파일 입출력 성능 최적화
파일 입출력 작업은 프로그램 성능에 큰 영향을 줄 수 있습니다. 특히 대량의 데이터 처리를 다룰 때 성능 최적화를 위해 효율적인 파일 입출력 방법을 사용하는 것이 중요합니다.
버퍼링을 활용한 최적화
파일 입출력에서 버퍼는 데이터를 한꺼번에 읽거나 쓰는 데 사용됩니다. 버퍼링을 통해 시스템 호출을 최소화하여 성능을 향상시킬 수 있습니다.
- 기본 버퍼링
C언어의 표준 입출력 함수(fgets
,fputs
,fprintf
, 등)는 자동으로 버퍼링을 수행합니다. 그러나 필요에 따라 버퍼 크기를 조정하거나, 비표준 버퍼링을 설정할 수 있습니다.
setvbuf(file, buffer, _IOFBF, buffer_size); // 완전 버퍼링 설정
- 버퍼링 비활성화
특정 상황에서는 버퍼링을 비활성화해야 할 수도 있습니다.
setvbuf(file, NULL, _IONBF, 0); // 비버퍼링 설정
대량 데이터 처리 시 블록 입출력
대량 데이터를 처리할 때는 줄 단위보다는 블록 단위로 데이터를 읽고 쓰는 것이 성능에 유리합니다.
fread
와fwrite
를 사용하면 블록 단위 데이터 작업이 가능합니다.
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
예제: 블록 단위 파일 읽기 및 쓰기
#include <stdio.h>
#include <stdlib.h>
int main() {
const char *input_file = "input.dat";
const char *output_file = "output.dat";
FILE *in, *out;
char buffer[1024];
size_t bytes_read;
// 파일 열기
in = fopen(input_file, "rb");
out = fopen(output_file, "wb");
if (in == NULL || out == NULL) {
perror("파일 열기 실패");
return 1;
}
// 블록 단위로 읽고 쓰기
while ((bytes_read = fread(buffer, 1, sizeof(buffer), in)) > 0) {
fwrite(buffer, 1, bytes_read, out);
}
fclose(in);
fclose(out);
printf("파일 복사가 완료되었습니다.\n");
return 0;
}
캐시와 비동기 입출력
- 캐시 활용
운영체제의 파일 시스템 캐시를 활용하면 디스크 접근 속도를 줄일 수 있습니다. - 비동기 입출력
멀티스레딩이나 비동기 입출력을 사용하면 CPU와 디스크 입출력 간의 병렬 처리를 통해 성능을 향상시킬 수 있습니다.
추가적인 성능 최적화 팁
- 작업 크기 조정: 작업 단위를 적절히 설정하여 과도한 입출력 호출을 줄입니다.
- 직접 입출력 사용: 특정 상황에서는 표준 라이브러리 대신 OS의 저수준 API를 사용하는 것이 더 빠를 수 있습니다.
- 압축: 데이터 파일 크기를 줄여 디스크와 메모리 사용량을 감소시킵니다.
성능 최적화를 통해 텍스트 파일 입출력 작업의 효율성을 크게 개선할 수 있습니다. 다음 항목으로 진행할까요?
요약
C언어의 텍스트 파일 입출력 함수(fprintf
, fscanf
, fgets
, fputs
)는 데이터를 효율적으로 저장하고 읽어오는 데 핵심적인 도구입니다. 이 기사에서는 파일 입출력의 기본 개념부터 주요 함수의 활용법, 주의사항, 그리고 성능 최적화 방법까지 살펴보았습니다.
정확한 함수 사용법과 파일 작업 흐름을 이해하면 프로그램의 안정성과 효율성을 모두 향상시킬 수 있습니다. 실습과 응용 예제를 통해 실제 개발에서도 유용하게 활용할 수 있습니다.