[C++] cout、wcout無法正常輸出中文字符問題的深入調查(1):各種編譯器測試


作者: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

 


源碼下載——
http://files.cnblogs.com/zyl910/wchar_crtbug.rar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM