파일 입출력 — 데이터 영구 저장
지금까지는 프로그램이 종료되면 모든 데이터가 증발했습니다. 이제 하드디스크에 데이터를 텍스트(.txt)나 바이너리(.dat) 형태로 기록하고 불러오는 방법을 배워, 영구적인 데이터베이스의 기초를 마련합니다.
학습 목표
FILE * 자료형을 이해하고, 파일 포인터의 역할을 파악한다.fopen()과 fclose()를 사용하여 안전하게 파일을 열고 닫는다."r", "w", "a", "rb", "wb")의 차이를 이해한다.fprintf(), fscanf(), fgets() 등 텍스트 기반 I/O를 수행한다.fwrite(), fread()로 구조체 배열을 통째로 바이너리 I/O로 저장한다.파일 입출력이 필요한 이유
우리가 지금까지 만든 구조체 배열(예: 회원 목록)은 오직 컴퓨터의 RAM(메모리) 위에만 존재합니다. 따라서 프로그램을 끄는 순간 모두 휘발(삭제)됩니다.
이를 방지하기 위해 데이터를 HDD나 SSD와 같은 보조기억장치에 기록하는 기술이 바로 파일 입출력(File I/O)입니다.
파일 열고 닫기 (fopen & fclose)
파일 작업을 할 때는 반드시 파일을 열고(fopen), 성공적으로 열렸는지 NULL 체크를 한 뒤 작업을 수행하고, 마지막에 반드시 닫아야(fclose) 합니다. (안 닫으면 파일이 깨지거나 잠길 수 있습니다.)
FILE *fp; // "w" (Write) 모드로 data.txt 파일을 엽니다. // 기존에 파일이 있으면 내용을 싹 다 지우고 새로 씁니다! fp = fopen("data.txt", "w"); if (fp == NULL) { printf("파일 열기 실패!\n"); return 1; } /* ... 데이터 쓰기 작업 ... */ fclose(fp); // 반드시 닫기!
"w" 모드로 파일을 열면 기존 내용이 묻지도 따지지도 않고 초기화(삭제)됩니다. 기존 내용 뒤에 이어서 기록하려면 반드시 "a"(Append) 모드를 써야 합니다.
텍스트 파일 읽고 쓰기
| 구분 | 쓰기 (Write) | 읽기 (Read) |
|---|---|---|
| 형식 지정 | fprintf(fp, "%d %s", id, name)printf()의 파일 버전 |
fscanf(fp, "%d %s", &id, name)scanf()의 파일 버전 |
| 문자열 1줄 | fputs("Hello", fp) |
fgets(buf, 100, fp) |
FILE *fp = fopen("student.txt", "w"); if (fp != NULL) { // printf처럼 쓰되, 맨 앞에 fp만 넣어주면 파일로 출력됩니다. fprintf(fp, "학번: %d\n", 1001); fprintf(fp, "이름: %s\n", "홍길동"); fclose(fp); }
바이너리 I/O: 구조체를 통째로 저장하기
텍스트 파일(.txt)은 사람이 메모장으로 열어볼 수 있지만 읽고 쓰기가 번거롭습니다. C언어는 구조체 데이터(메모리 덩어리)를 그대로 바이트 단위로 복사하여 파일(.dat)에 밀어 넣는 강력한 함수 fwrite()와 fread()를 제공합니다.
이때 파일 열기 모드는 반드시 "wb" (write binary), "rb" (read binary)를 써야 합니다.
Student students[3] = { /* ... 데이터 ... */ }; FILE *fp = fopen("db.dat", "wb"); if (fp != NULL) { /* 인자 설명: 1. 저장할 배열 주소 (students) 2. 덩어리 1개의 크기 (sizeof(Student)) 3. 덩어리 개수 (3명) 4. 파일 포인터 (fp) */ fwrite(students, sizeof(Student), 3, fp); fclose(fp); }
읽어올 때는 똑같은 인자 순서로 fread()만 쓰면 순식간에 구조체 배열이 복구됩니다!
파일 위치 제어 (fseek, ftell)
파일을 순차적으로 읽지 않고, "파일의 중간이나 맨 끝"으로 단숨에 커서를 이동시켜야 할 때 사용합니다.
ftell(fp): 파일 내 현재 커서가 어디 있는지 바이트(Byte) 단위의 위치를 알려줍니다.fseek(fp, 0, SEEK_END): 커서를 파일의 맨 끝으로 점프시킵니다. (파일 전체 용량을 알아낼 때 자주 씀)rewind(fp): 커서를 파일의 맨 처음으로 되돌립니다.
STEP 12 최종 프로젝트
파일 기반 회원 관리 프로그램 완성
STEP 11에서 만들었던 구조체 기반 메모리 프로그램을 확장하여, 실행할 때 기존 데이터를 로드(Load)하고, 종료할 때 저장(Save)하는 진짜 애플리케이션을 완성합니다.
├── 1. 시작 시
loadMembers()로 "db.dat"에서 fread() 수행├── 2. 메뉴 시스템 루프 (회원 등록 / 목록 조회 / 검색)
└── 3. [6] 종료 선택 시
saveMembers()로 fwrite() 수행 후 종료
STEP 12 완료 체크리스트
지금 만든 구조체 + 바이너리 파일 입출력 로직은, 훗날 여러분이 SQL 데이터베이스나 네트워크 스토리지 시스템을 다룰 때 겪게 될 직렬화(Serialization)/역직렬화(Deserialization) 개념의 완전한 기초가 됩니다. 다음 단계에서는 프로그램 실행 중에 필요한 만큼만 메모리를 주문하는 동적 메모리 할당의 세계로 떠납니다!