1. 파일 입출력의 이해
파일 입출력은 프로그램 실행 후에도 데이터를 보존하기 위한 방법입니다. 메모리의 데이터는 프로그램 종료 시 사라지지만, 파일에 저장하면 영구적으로 보관할 수 있습니다.
파일이란?
파일은 보조 기억장치에 저장된 데이터의 집합입니다.
| 구분 | 메모리 | 파일 |
|---|---|---|
| 저장 위치 | RAM (주기억장치) | HDD/SSD (보조기억장치) |
| 데이터 보존 | 프로그램 종료 시 소멸 | 영구 보존 |
| 접근 속도 | 빠름 | 상대적으로 느림 |
| 용량 | 제한적 | 대용량 |
| 용도 | 실행 중 데이터 처리 | 데이터 저장 및 공유 |
• 프로그램 종료 후에도 데이터 유지
• 대용량 데이터 처리
• 프로그램 간 데이터 공유
• 설정 정보 저장
텍스트 파일 vs 바이너리 파일
C 언어는 두 가지 방식으로 파일을 처리합니다.
| 구분 | 텍스트 파일 | 바이너리 파일 |
|---|---|---|
| 저장 형식 | 문자 형태 (ASCII/UTF-8) | 이진 형태 (바이트) |
| 가독성 | 텍스트 에디터로 확인 가능 | 전용 프로그램 필요 |
| 크기 | 상대적으로 큼 | 상대적으로 작음 |
| 예시 | .txt, .csv, .log | .exe, .bin, .dat |
| 변환 | 숫자 ↔ 문자열 변환 발생 | 메모리 그대로 저장 |
// 숫자 100을 저장할 때의 차이
// 텍스트 파일: '1', '0', '0' (3바이트)
fprintf(fp, "%d", 100);
// 바이너리 파일: 0x00000064 (4바이트, int 크기)
int num = 100;
fwrite(&num, sizeof(int), 1, fp);
2. 파일 열기와 닫기
파일 입출력의 기본은 파일을 열고 작업 후 닫는 것입니다.
fopen 함수
fopen 함수는 파일을 열고 FILE 포인터를 반환합니다.
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
- 매개변수 1: 파일 경로 및 이름
- 매개변수 2: 파일 열기 모드
- 반환값: 파일 포인터 (FILE *), 실패 시 NULL
파일 열기 모드
| 모드 | 설명 | 파일 없을 때 | 파일 있을 때 |
|---|---|---|---|
| “r” | 읽기 전용 | 실패 (NULL) | 처음부터 읽기 |
| “w” | 쓰기 전용 | 새로 생성 | 기존 내용 삭제 |
| “a” | 추가 쓰기 | 새로 생성 | 끝에 추가 |
| “r+” | 읽기/쓰기 | 실패 (NULL) | 처음부터 읽기/쓰기 |
| “w+” | 읽기/쓰기 | 새로 생성 | 기존 내용 삭제 |
| “a+” | 읽기/추가 | 새로 생성 | 끝에 추가 |
바이너리 모드: 위 모드에 b를 추가 (예: "rb", "wb", "ab")
•
"w" 모드는 기존 파일 내용을 모두 삭제합니다!• 파일 열기 실패 시 반드시 NULL 체크 필요
• 파일 경로는 절대 경로 또는 상대 경로 사용
fclose 함수
fclose 함수는 열린 파일을 닫고 버퍼를 비움니다.
#include <stdio.h>
int fclose(FILE *stream);
- 매개변수: 닫을 파일 포인터
- 반환값: 성공 시 0, 실패 시 EOF
기본 사용 예제
#include <stdio.h>
int main(void) {
FILE *fp;
// 파일 열기
fp = fopen("example.txt", "w");
if (fp == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
printf("파일이 성공적으로 열렸습니다.\n");
// 파일 닫기
fclose(fp);
printf("파일이 닫혔습니다.\n");
return 0;
}
실행 결과 보기
파일이 성공적으로 열렸습니다. 파일이 닫혔습니다.
실행 후 현재 디렉터리에 빈 파일 example.txt가 생성됩니다.
1.
fopen()으로 파일 열기2. NULL 체크로 오류 확인
3. 파일 읽기/쓰기 작업
4.
fclose()로 파일 닫기
3. 텍스트 파일 입출력
텍스트 파일은 사람이 읽을 수 있는 문자 형태로 데이터를 저장합니다.
fprintf 함수
fprintf 함수는 형식화된 데이터를 파일에 쓰기합니다.
#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);
- 매개변수 1: 파일 포인터
- 매개변수 2: 형식 문자열 (printf와 동일)
- 매개변수 3~: 출력할 데이터
- 반환값: 출력한 문자 수, 실패 시 음수
#include <stdio.h>
int main(void) {
FILE *fp;
fp = fopen("data.txt", "w");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
// 파일에 데이터 쓰기
fprintf(fp, "이름: %s\n", "홍길동");
fprintf(fp, "나이: %d세\n", 25);
fprintf(fp, "점수: %.2f점\n", 95.5);
fclose(fp);
printf("파일에 데이터를 저장했습니다.\n");
return 0;
}
실행 결과 및 파일 내용
파일에 데이터를 저장했습니다.
data.txt 파일 내용:
이름: 홍길동 나이: 25세 점수: 95.50점
fscanf 함수
fscanf 함수는 파일에서 형식화된 데이터를 읽기합니다.
#include <stdio.h>
int fscanf(FILE *stream, const char *format, ...);
- 매개변수 1: 파일 포인터
- 매개변수 2: 형식 문자열 (scanf와 동일)
- 매개변수 3~: 데이터를 저장할 변수의 주소
- 반환값: 읽은 항목 수, 파일 끝이면 EOF
#include <stdio.h>
int main(void) {
FILE *fp;
char name[50];
int age;
double score;
fp = fopen("data.txt", "r");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
// 파일에서 데이터 읽기
fscanf(fp, "이름: %s\n", name);
fscanf(fp, "나이: %d세\n", &age);
fscanf(fp, "점수: %lf점\n", &score);
printf("=== 파일에서 읽은 데이터 ===\n");
printf("이름: %s\n", name);
printf("나이: %d세\n", age);
printf("점수: %.2f점\n", score);
fclose(fp);
return 0;
}
실행 결과 보기
=== 파일에서 읽은 데이터 === 이름: 홍길동 나이: 25세 점수: 95.50점
fgets와 fputs 함수
fputs: 문자열을 파일에 쓰기
int fputs(const char *str, FILE *stream);
fgets: 파일에서 문자열 읽기
char *fgets(char *str, int size, FILE *stream);
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *fp;
char buffer[100];
// 파일 쓰기
fp = fopen("memo.txt", "w");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
fputs("첫 번째 줄\n", fp);
fputs("두 번째 줄\n", fp);
fputs("세 번째 줄\n", fp);
fclose(fp);
// 파일 읽기
fp = fopen("memo.txt", "r");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
printf("=== 파일 내용 ===\n");
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
return 0;
}
실행 결과 보기
=== 파일 내용 === 첫 번째 줄 두 번째 줄 세 번째 줄
• 형식화된 데이터:
fprintf, fscanf• 문자열 단위:
fputs, fgets• 문자 단위:
fputc, fgetc•
fgets는 개행 문자(\n)까지 읽음
4. 바이너리 파일 입출력
바이너리 파일은 메모리의 데이터를 그대로 저장하여 효율적입니다.
fwrite 함수
fwrite 함수는 메모리 블록을 파일에 쓰기합니다.
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
- 매개변수 1: 쓸 데이터의 주소
- 매개변수 2: 각 항목의 크기 (바이트)
- 매개변수 3: 항목 개수
- 매개변수 4: 파일 포인터
- 반환값: 쓴 항목 개수
fread 함수
fread 함수는 파일에서 메모리 블록으로 읽기합니다.
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
- 매개변수 1: 읽은 데이터를 저장할 주소
- 매개변수 2: 각 항목의 크기 (바이트)
- 매개변수 3: 항목 개수
- 매개변수 4: 파일 포인터
- 반환값: 읽은 항목 개수
기본 사용 예제
#include <stdio.h>
int main(void) {
FILE *fp;
int numbers[5] = {10, 20, 30, 40, 50};
int read_numbers[5];
int i;
// 바이너리 쓰기
fp = fopen("numbers.dat", "wb");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
fwrite(numbers, sizeof(int), 5, fp);
fclose(fp);
printf("데이터를 바이너리 파일에 저장했습니다.\n");
// 바이너리 읽기
fp = fopen("numbers.dat", "rb");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
fread(read_numbers, sizeof(int), 5, fp);
fclose(fp);
printf("읽은 데이터: ");
for (i = 0; i < 5; i++) {
printf("%d ", read_numbers[i]);
}
printf("\n");
return 0;
}
실행 결과 보기
데이터를 바이너리 파일에 저장했습니다. 읽은 데이터: 10 20 30 40 50
구조체 저장하기
바이너리 파일은 구조체를 통째로 저장하기에 매우 유용합니다.
#include <stdio.h>
#include <string.h>
typedef struct {
char name[30];
int age;
double score;
} Student;
int main(void) {
FILE *fp;
Student students[3] = {
{"김철수", 20, 85.5},
{"이영희", 22, 90.0},
{"박민수", 21, 88.3}
};
Student read_students[3];
int i;
// 구조체 배열 저장
fp = fopen("students.dat", "wb");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
fwrite(students, sizeof(Student), 3, fp);
fclose(fp);
printf("학생 데이터를 저장했습니다.\n\n");
// 구조체 배열 읽기
fp = fopen("students.dat", "rb");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
fread(read_students, sizeof(Student), 3, fp);
fclose(fp);
printf("=== 읽은 학생 데이터 ===\n");
for (i = 0; i < 3; i++) {
printf("이름: %s, 나이: %d세, 점수: %.1f점\n",
read_students[i].name,
read_students[i].age,
read_students[i].score);
}
return 0;
}
실행 결과 보기
학생 데이터를 저장했습니다. === 읽은 학생 데이터 === 이름: 김철수, 나이: 20세, 점수: 85.5점 이름: 이영희, 나이: 22세, 점수: 90.0점 이름: 박민수, 나이: 21세, 점수: 88.3점
• 메모리 구조 그대로 저장 (빠름)
• 텍스트 변환 없이 효율적
• 구조체, 배열 등 복잡한 데이터 저장에 유리
• 파일 크기가 작음
5. 파일 위치 제어
파일의 특정 위치로 이동하여 읽기/쓰기를 할 수 있습니다.
fseek 함수
fseek 함수는 파일 위치 지시자를 이동합니다.
#include <stdio.h>
int fseek(FILE *stream, long offset, int origin);
- 매개변수 1: 파일 포인터
- 매개변수 2: 이동할 바이트 수 (offset)
- 매개변수 3: 기준 위치
SEEK_SET: 파일 시작 (0)SEEK_CUR: 현재 위치SEEK_END: 파일 끝
- 반환값: 성공 시 0, 실패 시 -1
ftell 함수
ftell 함수는 현재 파일 위치를 반환합니다.
#include <stdio.h>
long ftell(FILE *stream);
- 반환값: 파일 시작부터의 바이트 수
rewind 함수
rewind 함수는 파일 위치를 처음으로 이동합니다.
#include <stdio.h>
void rewind(FILE *stream);
사용 예제
#include <stdio.h>
int main(void) {
FILE *fp;
int numbers[5] = {10, 20, 30, 40, 50};
int value;
fp = fopen("position.dat", "wb");
fwrite(numbers, sizeof(int), 5, fp);
fclose(fp);
fp = fopen("position.dat", "rb");
// 세 번째 요소로 이동 (0부터 시작하므로 인덱스 2)
fseek(fp, sizeof(int) * 2, SEEK_SET);
fread(&value, sizeof(int), 1, fp);
printf("세 번째 값: %d\n", value);
// 현재 위치 확인
printf("현재 파일 위치: %ld 바이트\n", ftell(fp));
// 파일 끝에서 두 번째 요소로 이동
fseek(fp, -sizeof(int) * 2, SEEK_END);
fread(&value, sizeof(int), 1, fp);
printf("끝에서 두 번째 값: %d\n", value);
// 처음으로 이동
rewind(fp);
fread(&value, sizeof(int), 1, fp);
printf("첫 번째 값: %d\n", value);
fclose(fp);
return 0;
}
실행 결과 보기
세 번째 값: 30 현재 파일 위치: 12 바이트 끝에서 두 번째 값: 40 첫 번째 값: 10
• 대용량 파일에서 특정 부분만 읽기
• 파일 크기 계산:
fseek(fp, 0, SEEK_END); size = ftell(fp);• 랜덤 접근이 필요한 데이터베이스 구현
• 파일 수정 시 특정 위치만 업데이트
6. 실전 예제
예제 1: 성적 관리 프로그램
#include <stdio.h>
#include <string.h>
typedef struct {
char name[30];
int math;
int english;
int science;
} Student;
void saveStudents(Student students[], int count) {
FILE *fp = fopen("students.dat", "wb");
if (fp == NULL) {
printf("파일 저장 실패\n");
return;
}
fwrite(students, sizeof(Student), count, fp);
fclose(fp);
printf("학생 데이터를 저장했습니다.\n");
}
void loadStudents(Student students[], int *count) {
FILE *fp = fopen("students.dat", "rb");
if (fp == NULL) {
printf("파일이 없습니다.\n");
*count = 0;
return;
}
*count = fread(students, sizeof(Student), 100, fp);
fclose(fp);
printf("%d명의 학생 데이터를 불러왔습니다.\n", *count);
}
void printStudents(Student students[], int count) {
int i;
printf("\n=== 학생 성적표 ===\n");
printf("이름\t\t수학\t영어\t과학\t평균\n");
printf("==========================================\n");
for (i = 0; i < count; i++) {
double avg = (students[i].math + students[i].english + students[i].science) / 3.0;
printf("%s\t\t%d\t%d\t%d\t%.1f\n",
students[i].name,
students[i].math,
students[i].english,
students[i].science,
avg);
}
}
int main(void) {
Student students[3] = {
{"김철수", 85, 90, 88},
{"이영희", 92, 88, 95},
{"박민수", 78, 85, 82}
};
Student loaded_students[100];
int count;
// 데이터 저장
saveStudents(students, 3);
// 데이터 불러오기
loadStudents(loaded_students, &count);
// 출력
printStudents(loaded_students, count);
return 0;
}
실행 결과 보기
학생 데이터를 저장했습니다. 3명의 학생 데이터를 불러왔습니다. === 학생 성적표 === 이름 수학 영어 과학 평균 ========================================== 김철수 85 90 88 87.7 이영희 92 88 95 91.7 박민수 78 85 82 81.7
예제 2: 로그 파일 작성
#include <stdio.h>
#include <time.h>
void writeLog(const char *message) {
FILE *fp;
time_t now;
struct tm *timeinfo;
fp = fopen("system.log", "a"); // 추가 모드
if (fp == NULL) {
printf("로그 파일 열기 실패\n");
return;
}
// 현재 시각 가져오기
time(&now);
timeinfo = localtime(&now);
// 시각과 메시지 기록
fprintf(fp, "[%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(fp);
}
void readLog(void) {
FILE *fp;
char buffer[200];
fp = fopen("system.log", "r");
if (fp == NULL) {
printf("로그 파일이 없습니다.\n");
return;
}
printf("=== 시스템 로그 ===\n");
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
}
int main(void) {
writeLog("시스템 시작");
writeLog("사용자 로그인");
writeLog("파일 업로드 완료");
writeLog("시스템 종료");
printf("로그를 기록했습니다.\n\n");
readLog();
return 0;
}
실행 결과 보기 (예시)
로그를 기록했습니다. === 시스템 로그 === [2024-01-15 14:30:25] 시스템 시작 [2024-01-15 14:30:25] 사용자 로그인 [2024-01-15 14:30:25] 파일 업로드 완료 [2024-01-15 14:30:25] 시스템 종료
7. 종합 실습
문제 1 - fopen 모드 (기초)
기존 파일 내용을 지우고 새로 쓰는 모드는?
A. "r"
B. "w"
C. "a"
D. "r+"
문제 2 - fprintf 사용 (기초)
다음 코드 실행 후 파일에 저장되는 내용은?
{% capture code_block2 %}
#include <stdio.h>
int main(void) {
FILE *fp = fopen("test.txt", "w");
fprintf(fp, "%d + %d = %d", 10, 20, 30);
fclose(fp);
return 0;
}
{% endcapture %}
문제 3 - fwrite 바이트 수 (중급)
다음 코드에서 파일에 쓰인 총 바이트 수는?
{% capture code_block3 %}
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.bin", "wb");
int arr[4] = {1, 2, 3, 4};
fwrite(arr, sizeof(int), 4, fp);
fclose(fp);
return 0;
}
{% endcapture %}
문제 4 - fscanf 사용 (중급)
다음 코드의 실행 결과는? (파일 내용: “100 200”)
{% capture code_block4 %}
#include <stdio.h>
int main(void) {
FILE *fp = fopen("numbers.txt", "r");
int a, b;
fscanf(fp, "%d %d", &a, &b);
printf("%d", a + b);
fclose(fp);
return 0;
}
{% endcapture %}
문제 5 - fseek 활용 (고급)
다음 코드의 실행 결과는?
{% capture code_block5 %}
#include <stdio.h>
int main(void) {
FILE *fp = fopen("test.bin", "wb");
int arr[5] = {10, 20, 30, 40, 50};
int value;
fwrite(arr, sizeof(int), 5, fp);
fclose(fp);
fp = fopen("test.bin", "rb");
fseek(fp, sizeof(int) * 3, SEEK_SET);
fread(&value, sizeof(int), 1, fp);
printf("%d", value);
fclose(fp);
return 0;
}
{% endcapture %}
문제 6 - 구조체 바이너리 저장 (고급)
다음 코드의 실행 결과는?
{% capture code_block6 %}
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
int main(void) {
FILE *fp = fopen("point.bin", "wb");
Point p1 = {5, 10};
Point p2;
fwrite(&p1, sizeof(Point), 1, fp);
fclose(fp);
fp = fopen("point.bin", "rb");
fread(&p2, sizeof(Point), 1, fp);
fclose(fp);
printf("%d", p2.x + p2.y);
return 0;
}
{% endcapture %}
핵심 요약
•
fopen(filename, mode): 파일 열기, FILE * 반환•
fclose(fp): 파일 닫기• 주요 모드:
"r"(읽기), "w"(쓰기), "a"(추가)• 바이너리:
"rb", "wb", "ab"2. 텍스트 파일 입출력
•
fprintf(fp, format, ...): 형식화된 쓰기•
fscanf(fp, format, ...): 형식화된 읽기•
fputs(str, fp): 문자열 쓰기•
fgets(str, size, fp): 문자열 읽기3. 바이너리 파일 입출력
•
fwrite(ptr, size, count, fp): 메모리 블록 쓰기•
fread(ptr, size, count, fp): 메모리 블록 읽기• 구조체, 배열 저장에 효율적
• 변환 없이 원본 데이터 그대로 저장
4. 파일 위치 제어
•
fseek(fp, offset, origin): 위치 이동•
ftell(fp): 현재 위치 반환•
rewind(fp): 처음으로 이동• origin:
SEEK_SET, SEEK_CUR, SEEK_END5. 주의사항
• 파일 열기 후 반드시 NULL 체크
• 사용 후 반드시
fclose() 호출•
"w" 모드는 기존 내용 삭제• 바이너리 모드에서는 반드시 "b" 추가
6. 활용 예시
• 데이터 영구 저장 (설정, 게임 저장)
• 로그 파일 작성
• 대용량 데이터 처리
• 프로그램 간 데이터 공유