2009. 6. 3. 17:48

구조체를 파일에 읽거나 쓸 때 알고 쓰자.

보통 구조체를 통해 파일을 읽고 쓸 수 있는 것은 알 것이다.
fwrite(&somestruct, sizeof (somestruct), 1, fp);
형식으로 쓰면 되는데 만약 구조체가 포인터( char*타입의 문자열이나 다른 구조체를 가리키고 있는 포인터)
포함하고 있다면 파일내의 데이터는 단지 주소값만 저장되기 때문에
fread가 이루어진다 해도 주소값만 저장되지 주소값이 가르키고 있는 값은 저장되지 않는다.
밑의 코드 예를 보자.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FILENAME1 "test1.txt"


typedef struct TmpData{

 int f;
 char arr[100];
 char* pArr;

}sTmpData;

typedef struct FileData{

 int a;
 int c;
 char b;
 sTmpData e;

}sData;


int WriteStFile(sData* in);
int ReadStFile(sData* out);


int main()
{
 sData WriteData = {2,33,'d'};
 sData ReadData;
 memset(&ReadData, 0, sizeof(ReadData) );

 // init WriteData
 WriteData.e.f = 100;
 strcpy( WriteData.e.arr, "aaa");
 WriteData.e.pArr = (char*)malloc( sizeof( WriteData.e.arr ) );
 strcpy( WriteData.e.pArr, "bbb");

 // Write File
 WriteStFile(&WriteData);

 // Read Structure Data from File
 ReadData.e.pArr = (char*)malloc( sizeof( WriteData.e.pArr ) );
 ReadStFile(&ReadData);

 // print
 printf("%d %d %c %d %s", ReadData.a, ReadData.c, ReadData.b, ReadData.e.f, ReadData.e.pArr);

 free( WriteData.e.pArr );
 
 system("pause");

 return 0;
}

int WriteStFile(sData *in)
{
 FILE* pFile = NULL;
 pFile = fopen( FILENAME1, "wb" );

 fwrite(in, sizeof(sData) , 1, pFile);

 fclose(pFile);

 return 0;
}

int ReadStFile(sData *out)
{
 FILE* pFile = NULL;
 pFile = fopen( FILENAME1, "rb" );

 fread(out, sizeof(sData) , 1, pFile);

 fclose(pFile);

 return 0;
}

실행은 하면 다음과 같은 결과가 뜬다.


위에 예상 결과와 달리 bbb라는 문자열이 잘 출력되고 있다. 무엇이 문제일까?
바로 WriteData 라는 구조체 변수에서 할당되었던 값을 의미하는 것이다.
즉 ReadData.e.pArr 가 가르키는 주소값과 WriteData.e.pArr 가 가르키는 주소값이 같기 때문에
아무 이상없이 출력되는 것이다. 혹 이것을 보고 bbb라는 문자열 또한 다른 메모리로 복사된 것으로
착각하기 쉽다.

위 코드에서 생성된 파일을 가지고 다음 코드를 주석처리한 다음 다시 실행해보자.

WriteData.e.pArr = (char*)malloc( sizeof( WriteData.e.arr ) );
strcpy( WriteData.e.pArr, "bbb");

결과를 보면


메모리에 값이 없거나 쓰레기 값, 또는 다른 곳에서 쓰고 있는 값을 가지고 올 수도 있는 것이다.

또한 많이 하는 실수는 메모리 해제에 있다.
같은 메모리를 가르키고 있는 상태를 그대로 쓴다면 메모리 해제에 있어 다음과 같은 실수를 저지를 수 있다.

 free( WriteData.e.pArr );
 free( ReadData.e.pArr );


즉 같은 메모리 주소를 가지고 두번 메모리 해제를 하기 때문에 힙 에러가 나는 것이다.

마지막으로 데이터 파일의 이식성을 위해서는 fopen()시 b 플러그를 사용하는 것이 좋다.