C, C++ 문법

WideCharToMultiByte, MultiByteToWideChar 메모리 할당 관련 리턴 값

디버그정 2009. 9. 4. 18:01

WideCharToMultiByte, MultiByteToWideChar와 관련해서
오해가 많고 헷갈리는 부분이 여섯번째 인수를 0으로 줬을 때의 리턴값이다.

리턴하는 값이 바로 인수로 집어넣을 수 있는 메모리 할당에 필요한 크기인지,
변환될 버퍼의 문자열 길이인지, 게다가 널터미네이트가 고려되었는지도 헷갈린다.

검색해서 여러 블로그와 웹페이지를 둘러보았는 데
문자열 변환결과는 맞게 나오지만 그 과정에서 잘못 의미를 이해하고 사용하는 경우도 많았다.

///////////////////////////////////////////////////////////////////

2019년 추가)

// WideCharToMultiByte, MultiByteToWideChar 함수는 작동이 독특하다. 특히 리턴값에 주목한다.
//
// 우선 주의할게 네번째 인수에 길이를 직접 입력하는 경우 널문자까지 포함해 '길이 + 1'을 입력해야 변환시 널문자까지 변환해 붙였다.
// 실제 길이만 전달하면 널문자는 변환하지 않아서인지 붙이지 않았다. 딱 지정한 길이만큼만 변환하는 식으로 작동한다.
// 이 인수에 -1을 입력하면 시스템이 알아서 널문자까지 변환해 붙여주므로 이 방식을 많이 이용한다.
// 굳이 길이만 입력하려면 버퍼를 0으로 초기화하거나 함수 수행 후 끝에 널문자를 수동으로 붙여야 한다.
//
// 다음은 네번째 소스 길이 인수에 -1 입력시 작동 형태와 리턴값이다.
// 버퍼 길이 > 출력 길이: 완전히 복사하고 널문자 붙이고 '출력 길이 + 1(널문자)'을 리턴한다.
// 버퍼 길이 = 출력 길이: 완전히 복사하고 버퍼가 다 찼으므로 널문자를 붙이지 않고 0을 리턴한다.
// 버퍼 길이 < 출력 길이: 버퍼 길이만큼만 복사하고 버퍼가 다 찼으므로 널문자를 붙이지 않고 0을 리턴한다.
// 성공시 리턴값은 출력 길이가 아니라 '출력 길이 + 1(널문자)'이다. 참고로 L"" 문자열을 변경해도 1이 리턴되었다.
// 버퍼 길이에 0을 입력하면 필요한 버퍼 길이를 구할 수 있는데 이 때에도 '출력 길이 + 1(널문자)'를 리턴한다.
// 이 경우 메모리 할당시 +1할 필요 없다. 다만 길이를 리턴하므로 sizeof(문자 타입)을 곱해야 한다.
//
// 참고로 소스 길이 인수에 널문제 제외한 길이만 입력시 첫번째, 두번째 경우 모두 출력 길이만큼 복사 후 끝에 널문자를 붙이지 않는다.
// 널문자를 변환에서 명시적으로 제외하므로 버퍼 길이와 출력 길이가 같아도 성공으로 판단하며 '출력 길이'를 리턴한다.
// 버프 길이 인수에 0을 입력한 경우에도 '출력 길이'를 리턴한다.
// 다만 이 경우 상술했듯이 수동으로 널문자를 붙여야 한다.

///////////////////////////////////////////////////////////////////////
그동안 미적지근하게 사용했던 것을 일거에 청소하고자
디버그해서 살펴보았다.

조사 결과 리턴값은  널 종료문자를 고려한 길이이다.
가령 "123가나" <====  옆의 문자열의 길이는 유니코드인 경우 5이고 멀티바이트인 경우는 7이다.
MultiByteToWideChar 를 여섯번째 인수를 0으로 줘서 리턴하는 결과값은
이경우 5가 아닌 6이다... 즉 널터미네이트가 이미 고려된 (순수문자열 길이+1)의 값이다.
기특하게 메모리 할당을 고려해서 리턴하고 있다. 물론 이 값을 바로 malloc등의 인수로 줘서는 안되고
*sizeof(WCHAR)을 해줘야 한다.

여러 웹사이트, 블로그에서 흔히 살짝 잘못 사용하는 경우가 저것을 널 종료가 고려되지 않는 문자열 길이로 인식해서
malloc((결과값+1)*sizeof(WCHAR)); 와 같이 사용하는 점이다.
이미 널종료를 포함하고 있으므로 굳이 +1을 해줄 필요는 없다.
(물론 한 개 더 할당했으므로 변환 결과는 달라지지 않을 것이다.)

아래는 시험해본 소스코드와 간편하게 사용할 때의 소스코드이다.
변환한 문자열이 짧은 경우라면 굳이 번거롭게 두번 실행하고 메모리 할당 함수를 사용할 필요가
없을 것이다. 

한가지 더 주의할게 결과 결과물의 길이 입력부분이다. 이 부분은 널 종료를 고려 안한 순수한 문자열의 길이다.

예를 들어 이 인수에 3를 설정하면 (문자 2개 + NULL)이 아나라 (문자 3개)를 출력한다.

그래서 버퍼길이 - 1 을 해줘야 버퍼를 초과하지 않는다.
 
//*
 char szAnsiStr[]="123가나";

 // 6째 인수가 0일시 리턴값은 문자열의 길이가 아니다.
 // 필요한 글자 카운트이다. 이미 널터미네이트를 포함했다. 즉 길이+1이다.
 // 그러므로 굳이 메모리할당시 (cNeeded+1)*sizeof(WCHAR)을 할 필요가 없다.
 // 또 사이즈라 생각하고 메모리 할당시 cNeeded만 인수로 줘도 안된다.
 int cNeeded = MultiByteToWideChar(CP_ACP, 0, szAnsiStr, -1, NULL, 0);
 WCHAR *lpszUniStr = (WCHAR*)malloc(cNeeded*sizeof(WCHAR));
 if(lpszUniStr){
  MultiByteToWideChar(CP_ACP, 0, szAnsiStr, -1, lpszUniStr, cNeeded);
  MessageBoxW(0, lpszUniStr, 0, 0);
  free(lpszUniStr);
 }
 
 return 0;
*/


 
 /*
 WCHAR szUniStr[]=L"abc아자";

 // 6째 인수가 0일시 리턴값은 문자열의 길이가 아니다.
 // 필요한 글자 카운트이다. 이미 널터미네이트를 포함했다. 즉 길이+1이다.
 // 그러므로 굳이 메모리할당시 (cNeeded+1)*sizeof(char)을 할 필요가 없다.
 // 멀티바이트는 카운트는 곧 사이즈이므로 메모리 할당시 cNeeded만 인수로 줘도 되지만
 // cNeeded*sizeof(char)이 보다 올바른 표현이다.
 int cNeeded = WideCharToMultiByte(CP_ACP, 0, szUniStr, -1, NULL, 0, NULL, NULL);
 char *lpszAnsiStr = (char*)malloc(cNeeded*sizeof(char));
 if(lpszAnsiStr){
  WideCharToMultiByte(CP_ACP, 0, szUniStr, -1, lpszAnsiStr, cNeeded, NULL, NULL);
  MessageBoxA(0, lpszAnsiStr, 0, 0);
  free(lpszAnsiStr);
 }
 return 0;
*/

 


 /*
 // 간편하게 MultiByteToWideChar 사용하는 법,,,
 // 명백히 짧은 문자열인 경우 굳이 메모리 할당과 길이를 구하는 과정을 할 필요가 없다.
 char szAnsiStr[]="123가나";
 WCHAR szUniStr[1024]={0};
 MultiByteToWideChar(CP_ACP, 0, szAnsiStr, -1, szUniStr, 1023);
 MessageBoxW(0, szUniStr, 0, 0);
 return 0;
*/
  /*
 // 간편하게 WideCharToMultiByte 사용하는 법,,,
 // 명백히 짧은 문자열인 경우 굳이 메모리 할당과 길이를 구하는 과정을 할 필요가 없다.
 WCHAR szUniStr[]=L"abc아자";
 char szAnsiStr[1024]={0};
 WideCharToMultiByte(CP_ACP, 0, szUniStr, -1, szAnsiStr, 1023, NULL, NULL);
 MessageBoxA(0, szAnsiStr, 0, 0);
 return 0;
*/