개요
C++에서는 텍스트 형식뿐 아니라 데이터를 바이트 단위로 그대로 저장하는 바이너리(binary) 파일 입출력도 지원합니다. 바이너리 모드를 활용하면 구조체나 배열 같은 데이터를 변환 과정 없이 파일에 직접 기록하고 다시 그대로 읽어올 수 있어 처리 속도가 빠르고 데이터 왜곡 위험도 적습니다.
바이너리 파일에 쓰기(Writing)
C++에서 바이너리 파일에 데이터를 기록하려면 write() 메서드를 사용합니다. 이 메서드는 지정한 스트림에 일정 바이트 수만큼 데이터를 기록하며, 기록 시작 위치는 'put 포인터'가 가리키는 자리입니다.
- put 포인터가 파일 끝에 있으면 파일이 자동으로 확장됩니다.
- put 포인터가 파일 중간을 가리키면 해당 위치의 기존 내용이 새 데이터로 덮어써집니다.
- 쓰기 도중 오류가 발생하면 스트림은 오류 상태(error state)로 전환됩니다.
write() 메서드 문법
ostream& write(const char*, int);
바이너리 파일에서 읽기(Reading)
바이너리 파일을 읽을 때는 read() 메서드를 사용합니다. 이 메서드는 지정한 스트림에서 일정 바이트 수만큼 데이터를 추출해 첫 번째 매개변수가 가리키는 메모리 공간에 저장합니다.
- 읽기 도중 오류가 발생하면 스트림이 오류 상태로 전환되며, 이후의 모든 읽기 작업은 실패합니다.
- gcount() 함수를 사용하면 지금까지 읽은 바이트 수를 확인할 수 있습니다.
- clear() 함수를 호출하면 스트림을 다시 사용 가능한 상태로 되돌릴 수 있습니다.
read() 메서드 문법
istream& read(const char*, int);
알고리즘
시작
학생 정보를 담을 Student 구조체를 선언한다.
쓰기용으로 바이너리 파일을 연다.
파일 열기 시 오류가 발생했는지 확인한다.
변수를 데이터로 초기화한다.
파일이 성공적으로 열렸으면 write() 메서드로 바이너리 데이터를 기록한다.
쓰기를 마치고 파일을 닫는다.
읽기용으로 바이너리 파일을 다시 연다.
파일 열기 시 오류가 발생했는지 확인한다.
파일이 성공적으로 열렸으면 read() 메서드로 바이너리 데이터를 읽어온다.
읽기를 마치고 파일을 닫는다.
오류 발생 여부를 검사한다.
데이터를 화면에 출력한다.
끝.
예제 코드
#include<iostream>
#include<fstream>
using namespace std;
// 학생 정보를 담는 구조체
struct Student {
int roll_no;
string name;
};
int main() {
// 바이너리 쓰기 모드로 파일 열기
ofstream wf("student.dat", ios::out | ios::binary);
if(!wf) {
cout << "Cannot open file!" << endl;
return 1;
}
Student wstu[3];
wstu[0].roll_no = 1;
wstu[0].name = "Ram";
wstu[1].roll_no = 2;
wstu[1].name = "Shyam";
wstu[2].roll_no = 3;
wstu[2].name = "Madhu";
// 구조체 크기만큼 바이너리 데이터 기록
for(int i = 0; i < 3; i++)
wf.write((char *) &wstu[i], sizeof(Student));
wf.close();
if(!wf.good()) {
cout << "Error occurred at writing time!" << endl;
return 1;
}
// 바이너리 읽기 모드로 파일 열기
ifstream rf("student.dat", ios::in | ios::binary);
if(!rf) {
cout << "Cannot open file!" << endl;
return 1;
}
Student rstu[3];
// 구조체 크기만큼 바이너리 데이터 읽기
for(int i = 0; i < 3; i++)
rf.read((char *) &rstu[i], sizeof(Student));
rf.close();
if(!rf.good()) {
cout << "Error occurred at reading time!" << endl;
return 1;
}
cout << "Student's Details:" << endl;
for(int i = 0; i < 3; i++) {
cout << "Roll No: " << rstu[i].roll_no << endl;
cout << "Name: " << rstu[i].name << endl;
cout << endl;
}
return 0;
}
실행 결과
Student's Details: Roll No: 1 Name: Ram Roll No: 2 Name: Shyam Roll No: 3 Name: Madhu
주의 사항
구조체 전체를 통째로 read/write 하는 방식은 편리하지만 몇 가지 유의할 점이 있습니다. 위 예제의 std::string처럼 내부에 포인터를 포함하는 멤버가 있는 구조체를 바이너리로 그대로 저장하면 실제 문자열이 아닌 포인터 주소값이 기록될 수 있습니다. 이 경우 컴파일러나 실행 환경이 달라지면 데이터가 올바르게 복원되지 않습니다. 따라서 실무에서는 char name[20]처럼 고정 크기 배열을 사용하거나, 각 멤버를 개별적으로 직렬화하는 방식이 더 안전합니다. 또한 파일을 열 때 반드시 ios::binary 플래그를 지정해야 운영체제에 따른 줄바꿈 문자 자동 변환을 피할 수 있습니다.