[C/C++] 구조체로 구성된 애플리케이션 정보를 디스크에 기록하는 법 1

이번 글은 구조체를 파일로 저장하고 읽어들이는 예제. 구성 원리는 간단하다. 파일 스트림을 사용해서 데이터를 읽고 쓴다. 여기에는 fwrite, fread라는 두 함수를 사용하는데 두 함수로 전달되는 인자는 동일하고, 반대의 작용을 할 뿐 원리는 동일하다.

fwrite : 특정 메모리 주소에서 시작하여 정해진 길이만큼, 또 그 개수만큼을 지정된 파일 스트림에 기록한다.

fwrite(&myVar, sizeof(myVar), 1, f)

혹은

fwrite(arrMyFriends, sizeof(elMyFriend), count, f)

와 같은 방식이다.

읽어올 때 사용하는 함수인 fread의 경우도 마찬가지인데, 특정한 길이만큼 메모리의 정해준 번지로 읽어들인다.

파일 스트림을 사용할 때는 연속적으로 읽고 쓰게 된다. 따라서 파일에는 그냥 무작정 기록만 할 것이 아니라 헤더 정보를 담는 구조체를 함께 저장하여 몇 개의 데이터가 있는지, 그리고 프로그램 버전에 따라 혹시 저장 포맷이 바뀔 수도 있으니 버전은 어떤지 등등을 체크해주는 부분을 넣으면 되겠다.

#include <stdio.h>
#include <string.h> //for memset(), strcpy()

void writeFriend();
void readFriend();

struct __friend {
	char name[20];
	int age;
	double height;
};

struct __friend friends[256] = 
{
	{"Lee Kiyoung", 30, 172.2f},
	{"Kim Taeyoung", 19, 182.5f},
	{"Choi Soonyeol", 25, 183.4f},
	{"Roh Minho", 38, 154.5f},
	{"", 0, 0},
};

struct __header {
	char desc[50];
	int ver;
	int count;
};

int num = 4;

int main(void)
{
	int sel, i;

	for(;;)
	{
		printf("1.view 2.save 3.delete 4.read 9.quit  ");
		sel = getchar();

		switch(sel)
		{
			case '1':
				for (i=0;i<num;i++) {
					printf("Name:%s, Age:%d, Height:%.1f\n", friends[i].name, friends[i].age, friends[i].height);
				}
				printf("Complete.\n\n");
				break;
			case '2':
				writeFriend();
				break;
			case '3':
				memset(friends, 0, sizeof(friends));
				num = 0;
				puts("\n\nAll records are deleted.");
				break;
			case '4':
				readFriend();
				break;
			case '9':
				return 0;
		}
	}

	return 0;
}

//	Saving structure array

void writeFriend()
{
	FILE *f;
	struct __header h;

	f = fopen("/Users/sooop/temp/sc/friend.dat", "wb");
	if(f == NULL)
	{
		puts("Can't create file");

	} else {
		strcpy(h.desc, "Friends");
		h.ver = 100;
		h.count = num;

		fwrite(&h, sizeof(struct __header), 1, f);
		fwrite(friends, sizeof(struct __friend), num, f);

		fclose(f);
		puts("saved.");
	}

}

void readFriend()
{
	FILE *f = NULL;
	struct __header h;

	f = fopen("/Users/sooop/temp/sc/friend.dat", "rb");
	if (f == NULL)
	{
		puts("Can't Open file");
	} else {
		memset(friends, 0, sizeof(friends));
		fread(&h, sizeof(struct __header), 1, f);
		if(strcmp(h.desc, "Friends") != 0)
		{
			puts("Not Address File");
			return;
		}
		if (h.ver != 100) {
			puts("Not match version");
			return;
		}
		num = h.count;
		fread(friends, sizeof(struct __friend),num,f);
		puts("read.");
	}

	if(f) fclose(f);

}

여기서 중요한 것은 보통의 프로그램에서 데이터를 담는 구조체는 실제 값을 갖기보다는 포인터를 가지고 있는 경우가 많다. 만약 __friend 구조체가 배열의 포인터만을 갖는다고 하면, 저장하거나 읽어들일 때는 문자열의 크기를 따로 저장해서 읽을 때도 이 정보를 사용해야 한다.

그럼 포인터를 멤버로 갖는 보다 많이 통용되는 모양의 구조체는 어떤 방식으로 디스크에 기록할 수 있을까? 그건 다음 글에서 살펴보기로 하자.