Computer >> 컴퓨터 >  >> 프로그래밍 >> C 프로그래밍

C/C++에서 바이너리 파일 읽기 및 쓰기 – write/read 메서드 완벽 정리


개요

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 플래그를 지정해야 운영체제에 따른 줄바꿈 문자 자동 변환을 피할 수 있습니다.