이번에 프로그램을 중국어로 포팅하면서 죽는 버그가 생겼는데 이 버그 잡느라 많이 삽질했습니다. XP에서 죽을 때 보면 모두 MFC 쪽 스택만 표시되서 UI 쪽이라는 의심은 했었는데 도저히 어딘지는 못찾겠더군요. 바꾼거도 별로 없는데, 이상하게 로그인만 하면 죽는겁니다 TT
Windows 2000에서 디버깅하면 뭔가 나타날줄 알았는데 스택이 똑같더군요 –;
아주 우연히 버그를 발견했는데…. 버그는
[CODE type=C]CString x;
string val = “haha”;
x.Format(“%s”, val);[/HTML][/CODE]
이런 거 였습니다. 뭐 위에 코드가 실행될때 바로 죽는거도 아니고, 몇번 실행되다 보면 이상해지는거죠. printf나 Format 자체가 뒤에 오는 인자를 컴파일러에서 체크할 방법이 없기 때문에 이런 오류는 컴파일러에서 워닝도 표시안해줍니다. 다만 gcc에서는 printf 등의 ANSI 함수에 대해서는 경고 표시는 해주죠.
하여간 아주 간단한 실수 하나로 정말 어의없게 디버깅해도 거의 찾을 수 없는 수준의 버그가 만들어지네요.
소스에서 한글로 되어 있는 부분을 문자열 리소스로 빼는 작업인데, 이를 위해서 GS() 함수와 GSTR() 함수를 만들었습니다. GS는 STL string을 리턴하고, GSTR은 C string을 리턴하도록 정의했습니다. 문자열 리소스 빼고나니 300개 정도 되는데 그중에 하나 실수한거죠.
[CODE type=C]extern CLanguage *gLanguage;
#define GSTR(x) gLanguage->GetString(x).c_str()
#define GS(x) gLanguage->GetString(x)[/HTML][/CODE]
이런 오류는 언제든지 다시 발생할수 있다는 생각에 구글에서 “type safe format”으로 검색해보니, boost에 format 라이브러리가 있더군요. 예전에 서브버전에 등록한 boost 버전에서는 없었던거 같은데… 하여간 라이브러리를 boost 1.34.1로 업데이트했습니다.
boost는 기본적으로 header 파일로만 구성된 template 라이브러리로 따로 컴파일을 할 필요없고, 몇가지 기능(예: regex, python, thread)이 필요하면 라이브러리를 컴파일해야합니다. 기본적인 기능은 거의 헤더 파일로만 구현되어 있어서 라이브러리 컴파일은 하지 않았습니다.
boost::format은 CString::Format이나 printf와 약간 사용법이 틀립니다. Format 등은 떨거지 인자들을 함수 인자로 쭈루루 전달하는데, 이렇게 전달하면 컴파일러에서 type checking을 할수 없기 때문에 문법이 약간 달라졌습니다. 처음에는 생소하지만, 조금만 익숙해지면 불편하지 않게 사용할수 있습니다.
[CODE type=C]#include <boost/format.hpp>
void test_format()
{
string str1 = (boost::format(“%02d:%s”) % 30 % “hello”).str();
CString str2;
str2.Format(“%02d:%s”, 30, “hello”);
CString str3 = str1.c_str();
}[/HTML][/CODE]
일단, 인자를 잘못 전달한다던가 해서 테스트해봤는데 죽는 경우는 없었습니다.
인자를 잘못 전달하더라도 컴파일러에서는 경고나 오류가 발생하지 않습니다. (VC 6.0 이라 그런지도 모르겠습니다.)
제일 큰 차이점은 인자들을 인자로 넘기는 것이 아니라 % 로 stream 형태로 넘기는 것입니다.
boost::format(“…”)으로 개체가 만들어지고 개체에 % 함수로 하나씩 인자를 넘기는 거죠. format의 인자(%d %s… 등등등)는 printf나 Format과 차이가 거의 없고, 그대로 사용하면 되고, 몇가지 추가 기능도 있습니다.
앞에 format의 타입과 뒤에 %로 전달되는 인자가 틀려도 알아서 잘(?) 표시 해줍니다. 내부적으로 C++ stream (<<)을 이용해서 변환하기 때문에 type이 안맞아도 그냥 동작하는거 같네요. %03d 등은 C++ stream manipulator 등을 이용할듯 하네요. 그래서 manipulator로 스트림 속성을 바꾸는 거기 때문에, 숫자 전달하는곳에 문자열을 전달해도 문제가 되지 않는듯합니다. stream manipulator는 C++ 책이나 http://www.deitel.com/articles/cplusplus_tutorials/20060218/index.html 를 참고하세요
.
시험 삼아 CString을 직접 전달해봤습니다. 죽지는 않고, 포인터 값을 출력하네요.
CString 넘길때마다 GetBuffer(0) 호출하기는 귀찮으니 operator <<를 만들어주면 CString을 직접 넘겨도 됩니다.
[CODE type=C]std::ostream& operator<<(std::ostream& os, const CString &x);[/HTML][/CODE]
구현은 쉬울거 같았는데, casting 때문에 좀 헤맸습니다.
[CODE type=C]std::ostream& operator<<(std::ostream& os, const CString &x)
{
os << const_cast<CString&>(x).GetBuffer(0);
return os;
}[/HTML][/CODE]
이렇게 하면 boost::format에 직접 CString을 넘겨도 문자열이 잘 출력됩니다.
사용하기 편리하도록 stdafx.h에 다음 라인들을 추가했습니다.
[CODE type=C]std::ostream& operator<<(std::ostream& os, const CString &x);
#define FORMAT_STRING(fmt, args) (boost::format(fmt) % args).str()
#define FORMAT_CSTRING(fmt, args) (FORMAT_STRING(fmt, args).c_str())
[/HTML][/CODE]
원래 소스를 boost::format으로 바꾸기 위해서는 일단 boost/format.hpp를 include 하고
아래처럼 바꾸면 됩니다.
CString x;
x.Format(“%02d 시”, cnt);
m_hourCtrl.AddString(x);
→
m_hourCtrl.AddString(FORMAT_CSTRING(“%02d 시”, cnt));
SendRequestFormat(“user %s|%s”, userid.c_str(), version.c_str());
→
SendRequest(FORMAT_STRING(“user %s|%s”, m_userid % version));