4. 파일 입출력
1. 파일 입출력 - ifstream, ofstream
파일의 입력과 출력은 스트림을 통해 관리되고, 사용자는 스트림을 열어서 파일을 관리할 수 있다.
ifstream 은 파일을 읽어들이는 스트림 클래스로, 이를 통해 파일 스트림을 열어서 읽어들일 수 있다.
ifstream::open(const char*, ios::openmode) 함수는 첫번째 인자로 파일의 경로를 받고, 두번째 인자로 파일을 어떻게 읽을 것인지를 받는다.
만약 파일을 바이너리 데이터로 읽고 싶다면, std::ios::binary 모드를 사용하면 된다. (MSDN)
ostream 은 파일을 내보내는 스트림 클래스이며, 파일을 쓴 후 내보내는 데 주로 사용된다.
파일을 쓰는 함수 write() 는 출력할 데이터 버퍼 char* 와 그 크기를 입력받는데, 반드시 배열 타입이어야 하는 것은 아니다.
예를 들어, 구조체를 선언해서 구조체 단위로 파일을 한번에 읽고 쓸 수 있다.
파일 입출력에서 가장 중요한 것은 마지막에 열었던 스트림을 close() 로 닫는 것이다. 스트림을 닫지 않으면 예기치 못한 문제가 발생할 수 있으니 반드시 닫아주도록 하자.
2. 응용
파일 입출력을 응용하여, 프로그램 시작 시 마지막 종료 전 저장해 둔 윈도우 위치와 해상도를 불러오는 기능을 구현했다.
// 프로그램 시작 시 Engine 의 초기화 과정 중 윈도우 위치와 해상도 정보를 가져온다.
ifstream DataFile;
DataFile.open("DNF_program.data", ios::binary);
// 파일이 열리지 않았을 경우 (해단 파일이 존재하지 않는 경우 등)
if (!DataFile.is_open())
{
// 기본값을 저장한 파일을 내보낸다.
ofstream DataFile2;
DataFile2.open("DNF_program.data", ios::binary);
ProgramInfo info2;
info2.Resolution = Vec2D(CEngine::GetInst()->GetResolution().x, CEngine::GetInst()->GetResolution().y);
info2.WindowPos = m_MainWndPos;
info2.Scale = CEngine::GetInst()->GetScreenScale();
DataFile2.write((char*)&info2, sizeof(info2));
DataFile2.close();
// 기본값을 저장한 뒤 파일 스트림을 반드시 닫아준다.
// 다시 파일을 열고 ProgramInfo 구조체로 파일을 한번에 읽어들인다.
DataFile.open("DNF_program.data", ios::binary);
}
ProgramInfo info;
DataFile.read((char*)&info, sizeof(info));
DataFile.close();
m_Resolution = info.Resolution;
m_ScreenScale = info.Scale;
m_MainWndPos = info.WindowPos;
// 프로그램 종료 메세지가 호출되면 파일에 윈도우 위치와 해상도 정보를 저장한다.
case WM_DESTROY:
{
PostQuitMessage(0);
// 종료 전 창 크기와 위치 기억
ofstream DataFile;
DataFile.open("DNF_program.data", ios::binary);
ProgramInfo info;
info.Resolution = Vec2D(CEngine::GetInst()->GetResolution().x, CEngine::GetInst()->GetResolution().y);
RECT rect;
GetWindowRect(CEngine::GetInst()->GetMainWnd(), &rect);
info.WindowPos = Vec2D(rect.left, rect.top);
info.Scale = CEngine::GetInst()->GetScreenScale();
DataFile.write((char*)&info, sizeof(info));
DataFile.close();
}
// 사용된 ProgramInfo 구조체
// pregma pack 을 이용해 데이터 패딩에 의한 공간 낭비를 최대한 줄인다.
#pragma pack(push, 1)
struct ProgramInfo
{
public:
Vec2D Resolution; // Vec2D 는 float 2개를 사용하는 2차원 좌표를 나타내는 구조체이다.
Vec2D WindowPos;
float Scale;
};
#pragma pack(pop)
// 구조체당 총 20 바이트를 사용한다.
해당 코드에서는 초기 실행 시 파일이 없는 것 등을 고려하여 파일 open 에 실패하면 기본 파일을 작성하도록 되어 있다.
하지만 이 경우 손상된 파일은 open 되어 잘못된 값을 읽어들일 수 있다.
이를 위해 파일 무결성 검사를 시행하는 기능을 추가하면 예외 상황을 처리할 수 있다.
파일 무결성 검사는 간단하게 체크섬(checksum) 방식을 이용해서 데이터 무결성 검사용 체크섬 바이트를 넣거나, SHA-256 등의 해시 알고리즘을 이용해서 해시값을 비교하여 무결성 검사를 하는 방법 등이 있다.