C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
분야별 포럼
C++빌더
델파이
파이어몽키
C/C++
프리파스칼
파이어버드
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

자유게시판
세상 살아가는 이야기들을 나누는 사랑방입니다.
[23703] 황당한 함수 _wcsicmp
박우성 [solgari] 24604 읽음    2013-01-15 18:34
저는 C++ 빌더를 10년 넘게 주개발툴로 사용하고 있습니다.

DLL 모듈은 Visual C++로 만들지만, 화면이 포함된 대부분의 작업은 C++ 빌더를 사용하고 있습니다.
그런데, _wcsicmp 함수 때문에 황당한 경우를 겪었습니다.
대소문자를 무시하고 문자열을 비교하고 싶어서, _wcsicmp(또는 _wcscmpi) 함수를 사용했는데 한글이 포함될 경우 엉뚱한 결과가 나옵니다.

	wchar_t *s1 = L"A가";
	wchar_t *s2 = L"A가나";
	char *s3 = "A가";
	char *s4 = "A가나";

	Memo1->Lines->Add( StrIComp( s1, s2 ) );
	Memo1->Lines->Add( UnicodeString( s1 ).CompareIC( s2 ) );
	Memo1->Lines->Add( _wcsicmp( s1, s2 ) );
	Memo1->Lines->Add( stricmp( s3, s4 ) );


C++빌더 XE에서 실행하면, 다음과 같은 결과가 나옵니다.

-45208
-1
0
-179


음수의 값이 나와야 되는 것이 정상인데, _wcsicmp함수만 이상한 결과가 나옵니다.
C++빌더 6과 2010에서 테스트를 해보니까, 마찬가지로  _wcsicmp함수만 결과가 0이 나옵니다.

Visual  C++에서는 정상적으로 모두 음수값이 출력되었습니다.

C++빌더와 Visual C++을 둘다 사용하면서 서로 소스를 마이그레이션 하거나, 복사할 경우를 대비해서 양쪽에 다 있는 함수를 우선적으로 사용하는데, 이건 믿는 도끼 발등 찍힌 기분이네요.

현재 제가 몸 상태가 안 좋아서 정신이 몽롱한 상태인데, 제가 뭘 잘 못한 거겠죠?

내일 해 보면 정상적으로 되었으면 좋겠네요.
아니면, 제가 개발한 모든 소스에서 _wcsicmp(또는_wcscmpi)를 찾아서 다 고쳐야 된다는....ㅠㅠ
박지훈.임프 [cbuilder]   2013-01-16 14:39 X
박우성님의 글을 보고, 이상하다 싶어서 이래저래 좀 뒤져봤습니다.
결론부터 말하자면, 프로그램 초기화시에 다음과 같이 한 줄을 추가해주면 _wcsicmp()에서 제대로 된 결과를 리턴할 겁니다.
setlocale(LC_CTYPE, "");

저도 아리까리합니다만, 어쨌든 C++빌더에서 _wcsicmp() 구현이 C 로케일의 영향을 받게 되어 있나봅니다.
그런데 C++빌더에서 이 로케일의 기본값이 시스템 디폴트 값이 아니라, "C"로 되어 있습니다.
이렇게 로케일 값이 "C"인 경우, _wcsicmp() 함수가 좀 다르게 동작하더군요.

setlocale() 함수는 여러가지 로케일 값들을 설정하는 함수인데, 첫번째로 넘겨줬던 LC_CTYPE은 설정할 로케일의 종류중 하나로서 문자의 취급에 대한 분류이고요, 두번째로 넘겨준 ""는 시스템 디폴트 로케일로 설정하라는 의미입니다.

그래서, 지금까지 작업하셨던 모든 소스에서 다 수정하기보다는, 모든 프로젝트의 초기화 부분에서 setlocale() 함수를 한번씩만 호출하기만 하면 될 것 같네요.
무능력 [sykelos]   2013-01-17 01:27 X
역시 임프님 대단하십니다..
근데 setlocale으로 해결할 수 있다는 것은 어떻게 아셨는지 궁금합니다
박지훈.임프 [cbuilder]   2013-01-17 02:45 X
저도 명쾌하게 이해가 안되어서 자세히 쓰지 않은 건데..
어리버리한 넘 잔머리의 바닥까지 들여다보시려고... ㅎㅎㅎ

_wcsicmp의 문제니까 제 머리의 동작 방식으로는 자연스럽게 _wcsicmp의 소스를 들여다보려고 했었죠.
근데 _wcsicmp 함수 자체의 소스는 없고, t로 시작하는 tchar 버전의 함수로 define이 되어 있더군요.

그리고 C++빌더에 포함된 소스들을 뒤져보니 그 함수의 소스가 있어서 들여다보니, locale.IsCLocale이던가 하는 구조체 멤버를 참조해서 그에 따라 if 문 분기를 하도록 되어 있더군요. 이 IsCLocale 멤버로 검색을 해보니 별다른 정보는 안나오고, 다만 일본의 C++빌더 개발자가 다른 함수 하나에서 비슷한 문제를 겪은 기록도 나오고요.

IsCLocale은 BOOL 타입으로 TRUE FALSE 값을 가질 수 있죠. TRUE이면 위에서 박우성님이 말씀하신 결과가 나오니까 FALSE로 설정되게 하면 되겠지요. 그래서 이 IsCLocale 멤버를 강제로 설정해보니 잘 되긴 했습니다. (IsCLocale이라는 이름 자체가 "로케일이 C이냐"라는 의미겠지요?)

그런데 그 정도로 마무리하려다 보니, 이 IsCLocale이 포함되어 있는 헤더파일 locale.h가 include 아래가 아닌 source/cpprtl/rtlinc 아래에 있어서 좀 불편하게 되어 있더군요. 물론 include가 아닌 다른 디렉토리에 있으면 #include <../source/cpprtl/rtlinc/locale.h> 이렇게 해버리면 되긴 하지만, 이건 좀 아니다 싶어서 다른 방법을 찾기 시작했습니다.

그래서 찾은 넘이 setlocale() 함수였습니다. 이넘을 찾게 된 것은, 앞서 말한 locale.h 헤더가 표준 헤더 디렉토리인 include 디렉토리가 아닌 다른 디렉토리에 있으므로 include에 뭔가 비슷한 역할로 사용할 수 있는 넘이 있을 거라고 추론을 한 거였구요. 그래서 찾은 것이 setlocale()이었죠. 이놈의 값을 위 댓글처럼 설정해보니 IsCLocale을 설정한 것과 똑같이 동작했습니다. 즉 IsCLocale의 값이 따라서 바뀌더군요.

제가 가장 이해가 안되었던 것은, 로케일에 왜 "C"라는 값이 디폴트인지였습니다.
검색을 해보니 이건 C++빌더 이외의 다른 컴파일러에서도 당연한 듯 한데요. 왜인지는 몰라도 어쨌든 당연한 것 같기는 합니다.

이제 아시겠지만 저도 별 대단할 게 없는 평범한 개발자입니다.
저도 궁금해서 아침부터 일도 접어두고 거의 두시간 정도 이래저래 뒤져보고 알아낸 거였네요.
Lyn [tohnokanna]   2013-01-17 02:54 X
이유는 뭐...  이런 문자열 관련 함수들은 (setlocale을 포함한) 은 C++이 아니라 C출신의 함수고 유니코드가 나오기 이전에 만들어진 C 소스와의 호환성이 필요했으니까요 ...
Lyn [tohnokanna]   2013-01-17 03:02 X
이게 골때리는게 소스에 있는 문자열 바이너리의 해석방법을 정하는것만이 아니라 시간출력함수, 소수점 출력, 통화기호 등에 몽땅 영향을 주거든요...

그래서 시스템 로케일로 하는게 제일 좋겠지만 기존과 출력 결과가 달라질 위험을 제거하기위해 디폴트는 "C" 입니다.

임프님이 말씀하신대로 LC_CTYPE만 변경하여 텍스트 해석 방법만 제어하시는게 가장 안전합니다.
박우성 [solgari]   2013-01-17 09:56 X
임프님.. 바쁘신데 시간내서 분석해 주신 것 너무 감사드립니다.

이유야 어찌 되었던간에 개발자 입장에서는 황당한 것은 사실이구요, 앞으로는 _wcsicmp함수는 사용하지 않을려고요.
StrIComp함수로 대체해서 사용하기로 했습니다.

일거리 늘었네요ㅠㅠ
박우성 [solgari]   2013-01-17 10:32 X
임프님 분석을 보고 매뉴얼을 찾아봤는데, C++빌더 메뉴얼에는 locale관련 내용이 없는데, MSDN에는 샘플소스까지 있습니다.

// crt_stricmp_locale.c
#include <string.h>
#include <stdio.h>
#include <locale.h>

int main() {
   setlocale(LC_ALL,"C");   // in effect by default
   printf("\n%d",_wcsicmp(L"ä", L"Ä"));   // compare fails
   setlocale(LC_ALL,"");
   printf("\n%d",_wcsicmp(L"ä", L"Ä"));   // compare succeeds
}

locale 설정이 잘못되면 비교실패가 되네요. 근데, 공교롭게도 비교실패의 결과로 C++빌더에서는 그냥 0을 리턴에 버립니다. 결과값만 보고 비교실패인지 알 방법이 없죠.
C++빌더를 주로 사용하다 보니까, 당연히 매뉴얼도 C++빌더에 있는 것을 참조하게 되는데, 앞으로는 MSDN 매뉴얼을 참조해야 할 것 같습니다.

엠바카데로에서 매뉴얼좀 잘 만들어 주면 좋겠네요.

+ -

관련 글 리스트
23703 황당한 함수 _wcsicmp 박우성 24604 2013/01/15
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.