作者:zyl910
C++標准為C++標准IO庫設計了十分完善的國際化文本處理機制。但在實際使用中,卻發現各種編譯器對它的支持性存在較大的差異,很多時候無法正確的輸出字符。於是我對此進行了深入的調查。
一、說明
1.1 測試程序
下面有一段很簡單的程序,分別利用cout、wcout、printf輸出字符串。具體代碼為——
#include <stdio.h> #include <locale.h> #include <wchar.h> #include <string> #include <iostream> using namespace std; const char* psa = "A漢字ABC"; const wchar_t* psw = L"W漢字ABC"; int main(int argc, char* argv[]) { // init. //ios::sync_with_stdio(false); // Linux gcc. locale::global(locale("")); //setlocale(LC_CTYPE, ""); // MinGW gcc. wcout.imbue(locale("")); // C++ cout << psa; cout.clear(); cout<<endl; wcout << psw; wcout.clear(); wcout<<endl; // C printf("\nC:\n"); printf("\t%s\n", psa); printf("\t%ls\n", psw); return 0; }
大家猜一猜這段程序的運行結果是什么?
1.2 理論結果
先根據C++標准,分析一下這段程序的理論結果。
在main函數中,首先執行了這兩行代碼對地區環境進行了初始化——
locale::global(locale("")); wcout.imbue(locale(""));
細節解釋——
1. locale(""):調用構造函數創建一個local,其中的空字符串具有特殊含義:使用客戶環境中缺省的locale(《C++標准程序庫—自修教程與參考手冊》P697)。例如在簡體中文系統上,會返回簡體中文的locale。
2. locale::global(locale("")):將“C++標准IO庫的全局locale”設為“客戶環境中缺省的locale”。注意它還會設置C標准庫的locale環境,造成與“setlocale(LC_ALL, "")”類似的效果(《C++標准程序庫—自修教程與參考手冊》P698)。
3. wcout.imbue(locale("")):使wcout使用“客戶環境中缺省的locale”。
就這樣,使C標准庫、C++標准IO庫(尤其是wcout)均正確的設置了地區環境,與客戶環境中缺省環境完全匹配。
隨后,使用C++標准IO庫的cout、wcout分別輸出窄字符串和寬字符串——
// C++ cout << psa; cout.clear(); cout<<endl; wcout << psw; wcout.clear(); wcout<<endl;
細節解釋——
1. 調用cout、wcout的clear成員函數是為了清除錯誤狀態,使后續輸出能正常運行。
2. 使用“cout<<endl”或“wcout<<endl”時,不僅會使輸出文本換行,而且還會執行flush成員函數,提交緩沖區中的數據。使得cout、wcout的輸出文本不會發生沖突。
最后,使用C標准庫的printf函數輸出窄字符串和寬字符串——
// C printf("\nC:\n"); printf("\t%s\n", psa); printf("\t%ls\n", psw);
所以,測試程序的運行結果應當為——
A漢字ABC
W漢字ABC
C:
A漢字ABC
W漢字ABC
注意為了更好區分C++標准IO庫與C標准庫的輸出結果,這里給printf加了個TAB字符。
二、測試VC2005
因VC2005是VC系列中第一個對C++03標准支持性較好的編譯器,先來測測它。
2.1 Debug版
在VC2005中以Debug模式編譯測試程序,執行結果為——
A
W
C:
A漢字ABC
W漢字ABC
可見C++的cout、wcout均無法正常輸出中文字符。
而C的printf都能正常輸出含中文字符的窄字符串與寬字符串。
2.2 Release版
將編譯配置改為“Release”模式,再編譯運行,神奇的事情發生了。執行結果為——
A漢字ABC
W漢字ABC
C:
A漢字ABC
W漢字ABC
Release版下全部通過,cout、wcout、printf均能正常輸出。
三、測試VC2008及更高版本的VC
在VC2008中編譯測試程序,執行結果為——
A漢字ABC
W漢字ABC
C:
A漢字ABC
W漢字ABC
全部通過,cout、wcout、printf均能正常輸出。然后測試了Release版,也是全部通過。看來VC2005的Bug已經被修正了。
隨后又測試了VC2010、VC2012,均是全部通過。
四、測試Windows中的MinGW
4.1 測試
使用GCC 4.6.2(MinGW(20120426))編譯測試程序,執行結果為——
A漢字ABC
W
C:
A漢字ABC
W
窄字符串都能正常輸出,但寬字符串都不能正常輸出。
4.2 修改代碼,使MinGW能正常顯示
將初始化代碼增加一行——
// init. locale::global(locale("")); setlocale(LC_CTYPE, ""); // MinGW gcc. wcout.imbue(locale(""));
再用MinGW編譯運行,執行結果為——
A漢字ABC
W漢字ABC
C:
A漢字ABC
W漢字ABC
全部通過了,cout、wcout、printf均能正常輸出。看來MinGW中的“locale::global(locale(""))”不會設置“setlocale(LC_ALL, "")”,必須手動調用。
用VC2008編譯剛才修改后的代碼,也是全部通過。多調用一次“setlocale(LC_ALL, "")”並不會造成破壞。
五、測試Linux下的gcc
5.1 測試
使用Linxu中的GCC編譯測試程序,執行結果為——
A漢字ABC
WIWABC
C:
A漢字ABC
W漢字ABC
cout、printf均能正常輸出,但wcout不能正常輸出。
5.2 修改代碼,使Linux下能正常顯示
將初始化代碼增加一行——
// init. ios::sync_with_stdio(false); // Linux gcc. locale::global(locale("")); wcout.imbue(locale(""));
再用gcc編譯運行,執行結果為——
A漢字ABC
W漢字ABC
C:
A漢字ABC
W漢字ABC
全部通過了,cout、wcout、printf均能正常輸出。
5.3 第2次修改代碼,使MinGW能正常顯示
切換回Windows,使用MinGW編譯剛才修改后的代碼,執行結果為——
A漢字ABC
C:
A漢字ABC
W
寬字符串又不能正常顯示了。
根據上次的經驗,將初始化代碼增加“setlocale”——
// init. ios::sync_with_stdio(false); // Linux gcc. locale::global(locale("")); setlocale(LC_CTYPE, ""); // MinGW gcc. wcout.imbue(locale(""));
再用MinGW編譯運行,執行結果為——
A漢字ABC
W漢字ABC
C:
A漢字ABC
W漢字ABC
終於全部通過了。
5.4 在Linux中測試第2次修改代碼
在Linux中測試第2次修改代碼,全部通過。
再用VC2008編譯剛才修改后的代碼,也是全部通過。
看來終於找到VC、MinGW、Linux下均有效的初始化方法了。可惜“ios::sync_with_stdio(false)”禁用同步后需要手動進行同步,會造成某些舊代碼工作不正常,該方法實用性不大。
六、測試Mac OSX下的gcc
使用Linxu中的GCC編譯測試程序,執行結果為——
這么簡單的程序,居然運行時報錯了。這是什么原因呢?
用gdb調試該程序。r運行,where顯示調用棧,list顯示源碼——
可以看出,是在執行“locale("")”時報錯的。
“locale("")”不是C++標准中規定的嗎,怎么連它都會報錯?
在網上搜索了一下,發現有人查過mac下的gcc源碼,它在注釋中明確寫了"Currently, the generic model only supports the "C" locale."——
http://stackoverflow.com/questions/1745045/stdlocale-breakage-on-macos-10-6-with-lang-en-us-utf-8
std::locale breakage on MacOS 10.6 with LANG=en_US.UTF-8
七、總結
雖然C++標准的設想十分完善,可惜各種編譯器的實現程度存在不少差異。甚至某些平台上連“locale("")”都不支持。
為了保證跨平台,慎用C++標准IO庫,最好盡可能的使用兼容性非常好的C標准庫。
參考文獻——
《ISO/IEC 9899:1999》(C99). ISO/IEC,1999. www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
《C++ International Standard - ISO IEC 14882 Second edition 2003》(C++03). ISO/IEC,2003-10-15.
《C++標准程序庫—自修教程與參考手冊》. Nicolai M.Josuttis 著,侯捷、孟岩 譯. 華中科技大學出版社,2002-09.
《std::locale breakage on MacOS 10.6 with LANG=en_US.UTF-8》. http://stackoverflow.com/questions/1745045/stdlocale-breakage-on-macos-10-6-with-lang-en-us-utf-8
《[C] 跨平台使用TCHAR——讓Linux等平台也支持tchar.h,解決跨平台時的格式控制字符問題,多國語言的同時顯示》. http://www.cnblogs.com/zyl910/archive/2013/01/17/tcharall.html