훈, IT 공부/C,C++,MFC

궁금점, 파일을 읽을때 NULL 문자를 읽을까?

IT훈이 2018. 5. 31.
반응형

NULL 문자!? 0x00


 최근에 계속 프로젝트를 하다가 상사분의 조언을 들었다. 프로젝트의 특정 부분에 관한 말씀이셨는데, 파일을 Read하는 부분이었다.  MFC에서 CFile을 이용해서 읽던 중 항상 읽어드리는 데이터를 저장하는 버퍼를 크기보다 'nLen+1' 하나 더 크게 항상 생성하였다. 이 부분에 의문의 두시고 질문은 하신 것이다. 나는 당연히 항상 기존문자열의 길이+1을 하여 저장을 하였기에 생각을 못했지만 실무자의 눈에는 썩 좋은 코드로 생각이 되지 않으셨던 것이다. 물론 유동적으로 사용하는 버퍼이면 NULL은 넣는것은 당연하지만 한번 읽는데 사용하는 버퍼의 경우에는 굳이 한바이트 더 넣어줄 이유는 없다는 것이다. 

 나의 경우에는 직접 경험하고 봐야지 기억을 하는 타입이라서 곧바로 나름 실험에 들어갔다.


※ 추가 180602

 내 코드만으로 볼 경우에는 충분히 오해할 수 있는 상황이다. 상사분 질문의 의도는 왜 이미 초기화가 된 공간에 [+1] 을 수행시켜 코드를 복잡하게 만드는건가?? 


# 문제의 코드

1
2
3
4
5
6
7
8
9
nLen = CFile::GetLength() // 임시로 경우를 설명하기 위한 것임..
  ... 생략
  ...
wchar_t *pszUnicode;            
char *pszMulti;
pszUnicode = new wchar_t[sizeof(wchar_t)*(nLen+1)];
pszMulti = new char[sizeof(char)*(nLen+1)];
memset(pszUnicode,0,sizeof(wchar_t)*(nLen+1));
memset(pszMulti,0,sizeof(char)*(nLen+1));
cs


# case 1

1
2
3
4
char *pszBuffer = "HELLO WORLD";
char szBuffer[MAX_PATH] = {0,};
 
strcpy(szBuffer, pszBuffer);
cs

# case 2
1
2
3
4
5
6
7
8
9
char *pszBuffer = "HELLO WORLD";
char *szBuffer = NULL;
 
int nLen = strlen(pszBuffer);
szBuffer = new char[nLen+1];
 
strcpy(szBuffer, pszBuffer);
 
delete [] szBuffer;
cs

case 1 과 case 2를 보면 잘못된 게 없는 코드이다. 
그러나 strcpy가 아니라 ReadFile 과 같은 파일 읽어오는 함수를 사용할때 주의해야할 점이 있다. 경우에 만약 잘못된 경우라면 아래의 경우이다.

# case 3

1
2
3
4
5
6
7
    char *pszBuffer = "HELLO WORLD";
    char *szBuffer = NULL;
 
    szBuffer = new char[MAX_LEN+1];
 
    strcpy(szBuffer, pszBuffer);
    delete [] szBuffer;
cs


case 3 에서는 쓸데없이 크기를 더 할당하였다. ( 지금 예제가 생각이 안나서 추후에 수정하도록.. )

# case 4
1
2
3
4
5
6
7
8
9
10
    TCHAR *pszPath = _T(".\test.txt");
    HANDLE h = CreateFile(pszPath, GENERIC_READ , FILE_SHARE_READ, 0, OPEN_EXISTING, 00);
 
    if( h != INVALID_HANDLE_VALUE)
    {
        char szBuffer[5+1= {0,};
        DWORD dwLen = 0;    
        ReadFile(h, szBuffer, 5&dwLen, 0);
        CloseHandle(h);
    }
cs

case 4의 경우에는 test.txt 파일의 내용이 "HELLOWORLD" 일 경우이다.

 6번 행에 보면 6개의 크기의 배열을 할당한 점을 확인할 수 있다. 왜?? 저렇게 했을까?? 이유는 읽은 데이터를 문자열로 사용하기 위해서이다. 지금 코드상으로는 파일의 "HELLO"를 읽을 수 있다. 또한 프로그램 안에서 읽은 데이터를 가지고 문자열처럼 사용가능하다. 그러나 6번 행을 char szBuffer[5= {0,}; 이렇게 정의 하였다면 이야기가 달라진다. 아래의 메모리를 보면 알 수 있다.

# memory 1

char szBuffer[5+1= {0,};

# memory 2

char szBuffer[5= {0,};


 한눈에 문제가 무엇인지 알 수 있다. memory 2를 보면 문자열의 끝은 널 이다 라는 개념을 무시하고 있다. 이 경우에는 컴퓨터는 멍청하기에 0x00CFED9E에 있는 NULL 문자를 만나야 문자열이라는 것을 인식한다.


 결론적으로 얻은 것은 메모리 할당에 관련하여서는 코드 전체를 이해하지 않고서는 판단히 힘들다. ( 옳고 그른 것에 대한 것). 
 상사분께서는 한눈에 남의 코드를 보고 그 코드에 대해서 뭐라하는 옳지는 않다고 하셨다. 그러나 프로그래머는 자신에 코드에 있어서 왜 이렇게 사용했는지는 설명할 줄 알아야 한다는 것을 말씀하고 싶으신 것이다.
 또한 생각하는 프로그래머가 되어야 한다는 것이 중점이다.



▶ 파일 입력 메모리 관련 실험 


환경 : Visual Studio 2008 을 이용하였고, 2013에서도 동일하다.

함수,클래스 : CFile, CreateFile, UTF-8 종류의 파일을 읽어들였다. 매 부분마다 메모리를 확인하였다. 주의하여서 볼 것은 각 파일의 내용이 다르기 때문에 파일 크기는 유동적이라는 점과


환경은 Alt+F7 단축키로 설정에 들어가서 변경가능하다.


[ 유니코드 환경 ]


 

 FileSize ( 파일크기 )

 해독 유/무 

 NULL 유/무 

 UNICODE 

 384

 모든문자 해독 가능 

 무 

 ANSI

 220 

 모든문자 해독 불가능

 무

 UTF-8

 252 

 모든문자 해독 불가능

 무 


[ 멀티바이트 환경 ]


 

 FileSize ( 파일크기 )

 해독 유/무 

 NULL 유/무 

 UNICODE

 384 

 모든문자 해독 불가능 

 무 

 ANSI

 220

 모든문자 해독 불가능

 무

 UTF-8

 252 

 한글제외하고 읽어짐

 무


▶ 결과


 문자열을 읽어올때 버퍼 속에는 NULL이 들어와있지 않았다. 왜 그런것인지 의문이 생긴다. 분명히 텍스트를 읽어오기 위해서는 NULL을 통하여 문자열의 끝을 확인한 데이터를 Read하였을 것이다. 그런데 읽어올때는 NULL만 제외하고 읽어온다...?? 뭔가 내가 모르는 어떤 부분이 있는것이가. 우선은 눈으로 확인한 결과가 나에게는 팩트이기 때문에, 어떤것이 확실한 답인지는 추후에 기록해 둘것이다.


추가 180602

+ ReadFile

이번 경우로 확실한 것은 ReadFile은 NULL을 포함하여 읽지 않는다는 점이다. 

그렇기 때문에 ReadFile을 할 경우에는

1. 데이터 보다 크게 공간을 확보하기.

2. 초기화 하기 ( 쓰레기값때문에 초기화 해주어야한다. 그렇지 않으면 문자열을 끝을 찾지못한다. )


+ strlen, strcpy 관련

strlen 은 문자열의 길이만 리턴하고( NULL 제외), strcpy는 문자열의 NULL까지 포함하여서 복사한다. 즉 이 두개를 같이 사용하기 위해서는 strlen+1 의 추가공간을 할당하고 0으로 반드시 초기화를 해주어야한다. 즉 동적인 크기를 사용하기 위해서는 초기화라는 개념과 문자열의 길이에 대한 개념이 잘 잡혀있어야 원하는 데이터를 얻는데 문제가 발생하지 않는다.


▶ 질문거리 

1. 그렇다면 전체를 읽는 것이 아니라 부분을 읽어들인다면 NULL은 넣어줘야하지 않는가? ( 나의 개념으로는 당연한 생각 이다. )     

답 : 당연하다. NULL을 넣어주지 않으면 컴퓨터는 멍청해서 NULL을 찾을때까지 읽을 것이다.





반응형

댓글