MBCS로 문자열을 다루다 보면 여러가지 복잡한 문제가 생길수 있(다고 하)는데...
그중 한가지를 오늘 겪게 되었다.

    CHAR    szBuf[10] = {0,};
    lstrcpyn (szBuf, "거거거거거거거거", _countof(szBuf)); 
    printf ("%s\r\n", szBuf);           // 여기서 BP!

아주 평범한 코드이다. 결과를 예상해보면...
버퍼가 10바이트이고 한글은 한글자가 2바이트니까 5글자가 버퍼에 복사될...까?
그런데 문제가 그렇게 간단하지가 않다.

실제 실행시켜 보면 결과가 이렇게 나온다.
거거거거?
Press any key to continue

어렵쇼? 마지막에 한글자가 깨졌네...

위의 lstrcpyn문이 실행된 직후에 BP를 걸고 메모리를 살펴보자.
사용자 삽입 이미지


위에서 보듯이... 메모리 앞부분 8바이트에는 "거 (0xB0 0xC5)"가 들어 있다. 그런데 다섯번째에는 글자의 두번째 바이트가 NULL로 바뀌어 있다. (0xB0 0x00) lstrcpyn 함수가 버퍼사이즈를 초과할 때 문자열을 종결시키기 위해 자동으로 NULL 을 Append하면서 마지막 "거"의 두번째 바이트만 NULL로 덮어씌워진 것이다.
말하자면... MBCS 문자(2바이트)와 NULL 문자(1바이트)의 사이즈가 다르기 때문에 발생하는 문제인 것이다.

So What?? 모 한글자 정도 깨진다고 해서 별 문제될 거 있나?
당연히 문.제.될.게.있.다!!!

다음의 코드를 보자.

    CHAR    szBuf[10] = {0,}, szSendString[1024] = {0,};
    lstrcpyn (szBuf, "거거거거거거거거", _countof(szBuf));
    _snprintf(szSendString, _countof(szSendString), "%s;%s;%s", 
                   "FIRST_DATA", szBuf, "LAST_DATA");
    printf ("%s\r\n", szSendString);
실행 결과는 다음과 같다.
FIRST_DATA;거거거거?LAST_DATA
Press any key to continue

어떠한가? 모 "거거거거"뒤에 한글자 깨진 것은 예상했던 바이지만 중요한 것은 그 뒤에 프린트되어야 할 구분자 ";" 이 없어져버렸다. 어디로 간 걸까?

메모리를 살펴보면 다음과 같다.
사용자 삽입 이미지


아... 마지막에 깨진 한글자의 남아있는 첫바이트(0xB0)와 한바이트 글자인 세미콜론(0x3B)가 합쳐져서 0xB0 0x3B 라는 이상한 멀티바이트 글자가 되어버린 것이다. 그렇다.. 세미콜론은 마지막 깨진 글자와 합쳐져 버린 것이다. 이렇게 구분자가 없어져 버렸으니 곤란하군 이거....

그렇다면 어떻게 해야 올바르게 동작할까?
아래와 같은 함수로 NULL-Terminate시켜주어야 한다.


BOOL   Terminate_MBCS(LPTSTR szParam, INT nSize)  
{ 
  if (szParam == NULL) 
    return ERROR_INVALID_HANDLE; 
 

#ifndef _UNICODE     
  for (int i=0; i < nSize; i++) 
  { 
    if ((BYTE)szParam[i] > 0x80)    
    {   // 여기 들어오는 케이스는 항상 MBCS의 첫바이트이다. 
      if (i == nSize - 2)  // 끝에서 2번째 바이트가 MBCS의 첫바이트라면.. 
      { 
        szParam[nSize - 2] = NULL; // NULL Terminate시킴 
        break; 
      } else { 
        i++;   // 끝에서 2번째 바이트는 아닌데, MBCS의 첫바이트라면 한바이트 건너뛰어라. 
      } 
    } 
  } 
#endif 

  return ERROR_SUCCESS; 
} 

위의 함수는 다음과 같이 사용한다.
    CHAR    szBuf[10] = {0,}, szSendString[1024] = {0,};
    lstrcpyn (szBuf, "거거거거거거거거", _countof(szBuf));
    Terminate_MBCS (szBuf, size(szBuf));
    _snprintf(szSendString, _countof(szSendString), "%s;%s;%s", 
                                "FIRST_DATA", szBuf, "LAST_DATA");
    printf ("%s\r\n", szSendString);

 


결과는 아래와 같다.

FIRST_DATA;거거거거;LAST_DATA
Press any key to continue

버퍼의 끝에서 두번째 바이트가 0x80 이상일 경우 이것은 멀티바이트 글자의 첫번째 Leading 바이트라는 의미이다. 따라서, 이 글자는 Terminating NULL 문자에 의해 깨져 있다는 것을 확인할 수 있다. 따라서 이 Byte를 NULL로 대체해주면 정상적으로 Terminate될 수 있다.

원래 isleadbyte라는 매크로 함수가 있긴 한데... 써보면 잘 동작하지 않는다. 혹시 어떻게 쓰는지 아시는 분 계시면 알려주시라..









Posted by kuaaan

댓글을 달아 주세요

  1. 나그네 2008.09.23 11:00  댓글주소  수정/삭제  댓글쓰기

    MSDN 내용입니다..
    isleadbyte returns a nonzero value if the argument satisfies the test condition or 0 if it does not. In the "C" locale and in single-byte character set (SBCS) locales, isleadbyte always returns 0.

    이기 때문에 제대로 된 사용을 할 수 없을 듯 합니다... 항상 0 값을 리턴해주니깐요..^^

    • kuaaan 2008.09.23 12:28 신고  댓글주소  수정/삭제

      음.. 그건 봤는데요...
      In the "C" locale and in single-byte character set (SBCS) locales, isleadbyte always returns 0. ==> 이말은 SBCS일 때는 항상 0을 리턴한다는 뜻 아닌가요? MBCS일 때는 동작해야 할 것 같은데.. ^^



사랑합니다. 편안히 잠드소서