Work | Posted by 흑목 2006/05/18 15:11

Buffering Error

컴프 게시판 답변

int main(void)
{
       FILE *fp;
       char *file="test.txt";
       char c;

       fp=fopen(file,"wt+");
       fprintf(fp,"haha");
       rewind(fp);
       
       while(1)
       {
               c=getc(fp);
               if (c==EOF) break;
               printf("%c ",c);
       }
       fclose(fp);


       return 0;
}

라는 코드를 컴파일 해보면 test.txt 파일에 haha라는 값이 저장됩니다.

그런데, rewind(fp); 부분을 쓰지 않으면 haha뒤로 쓰레기 값이 저장됩니다.

fprintf 뒤로는 파일에 영향을 줄만한 함수가 없다고 생각되는데 쓰레기 값이 저장되는 이유가 뭔가요?

getc하는 과정에서 생기는 쓰레기 값 같은데 아무리 생각해봐도 이해가 되질 않습니다. (이 함수는 읽어오는 함수이기때문에..)


그리고 위의 소스에서 wb를 하거나 wt를 했을때 결과값에 차이가 없는데

바이너리 모드와 텍스트 모드의 아웃풋 에서의 차이점은 무엇인가요


-----------------------------------------------------------------------------------

위의 코드를 봤을 때 다음과 같이 생각하는 것이 일반적입니다.

1. file에 4byte를 write를 하였음.
2. offset이 4byte 이동. offset은 file의 끝.
3. read를 함. file의 끝이므로 EOF를 받고 종료


아래의 답변은 지극히 저의 상상임을 밝혀둡니다.

standard I/O library는 file에 대해서는 fully bufferd를 합니다.

buffer가 꽉 차기 전까지는 write를 하지 않습니다.

fprintf를 call 했을 때 haha의 4byte 쓰기를 요쳥했지만 call할 당시엔 바로 쓰지 않습니다.
하지만 file의 offset은 증가합니다.

그리고 아래에서 1byte씩 read를 수행합니다. read를 해도 offset은 계속 증가하겠죠.

read를 할 때 EOF를 반환하면 멈추게 코드가 작성 되있는데 돌려보면 논리적으로 봤을 때는 파일의 끝이므로 EOF가 반환되어야 하지만 EOF가 반환되지 않고 어느정도 돌다가 우연히 EOF가 반환되어 멈추는 것을 알 수 있습니다. 여기서 우리는 EOF가 파일의 끝이라고 항상 return이 되지는 않음을 알 수 있습니다.(실제 EOF가 어떤 상황에서 return되는지는 library code를 봐야 합니다.)

위의 code에서는 read를 4092번하고 빠져나옵니다.

프로그램이 종료 된 후 test.txt 파일을 읽어보면 4096 byte가 써져 있음을 알 수 있습니다.

이 4096byte는 우리가 처음에 출력한 4byte "haha"와 4092번 read한 것과 관련이 있는것 같습니다.

여기서 우리는 하나의 가설을 새울 수 있습니다.

'buffering하고 있던 data를 write할 때에는 변한 offset만큼 write한다.'

검증을 위해 아래의 code를 돌려보았습니다.

int main(void)
{
FILE *fp;
char *file="test.txt";
char c;
int a = 0;

fp=fopen(file,"wt+");
fprintf(fp, "haha");

while(1)
{
c=getc(fp);
if (c==EOF) break;

printf("%c ",c);
a++;

if( a == 20 ) break;
}

fclose(fp);

return 0;
}

결과로 4byte "haha" + 20byte 값 총 24 byte가 찍힌걸 파일에서 확인 할 수 있었습니다.

위 문제의 해결 방법은 write를 수행한 후 바로 buffer를 flush를 해 줘서 read의 offset 변환이 buffer를 flush할 때 영향을 끼치지 않게 하는 것입니다.

어떤 조건에서 EOF를 반환하는지 library를 뜯어보지 않는 이상 알 수 없으므로 파일의 끝이라고 무조건 EOF를 리턴한다고 방심하면 안됩니다. 역시나 이와 같은 결과를 예측할 수 없는 code는 작성을 안하는것이 좋겠지요.

PS. rewind 같은 경우에는 구현이 어떻게 되있는지 모르나 내부에서 flush를 하는 것을 trace를 통해 알 수 있습니다. 따라서 rewind할 경우에는 쓰래기 값이 안붙죠.

-----------------------------------------------------------------------------------

바이너리 모드와 텍스트 모드의 차이점....

텍스트 모드일 경우에는 각 운영체제의 텍스트 파일 포맷에 맞게 실제 file을 읽고 쓸 때 변환이 이루어집니다.

예를 들면 실제 C에서는 모든 개행을 \n으로 처리합니다.

그러나 windows의 경우에는 개행이 파일에서 \r\n으로 이루어 지고, Linux의 경우에는 개행이 \n으로 이루어집니다.

따라서 \n을 쓴다고 했을 때 윈도우즈의 경우에는 \r\n으로 변환되어 Linux의 경우에는 \n으로 그대로 저장됩니다.(실제 \n을 출력하는 code를 작성하고 파일의 값을 확인해 보면 알 수 있습니다.)

바이너리 모드 같은 경우에는 포맷에 따른 변환 없이 값을 그대로 읽고 쓰게 됩니다.

따라서 Windows는 모드에따른 차이가 있지만, Linux는 차이가 없습니다.

'Work' 카테고리의 다른 글

Visual Studio의 fflush 이야기  (1) 2007/11/20
TimeAttack  (0) 2007/03/29
Buffering Error  (0) 2006/05/18
Builder Listview Column Sorting  (0) 2006/03/31
Variant Conversion  (0) 2006/03/31
"extern C"에 대하여  (0) 2006/03/30