天天看點

[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析

在不同平台上開發C/C++程式時,為了避免源碼檔案亂碼,得采用UTF-8編碼來存儲源碼檔案。但是很多編譯器對UTF-8源碼檔案相容性不佳,于是我做了一些測試,分析了最佳儲存方案。

  為了測試編譯器對UTF-8源碼檔案相容性,我編寫了這樣的一個測試程式——

[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析
[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析

  如果系統預設編碼是GB2312(如中文Windows系統),該程式的輸出結果應是——

len<1>=5,str=一字A // D2 BB D7 D6 41

len<2>=3,str=一字W // 4E00 5B57 0057

  如果系統預設編碼是UTF-8(如Linux系統),該程式的輸出結果應是——

len<1>=7,str=一字A // E4 B8 80 E5 AD 97 41

len<4>=3,str=一字W // 4E00 5B57 0057

  注:

1. “len”旁尖括号内的是字元類型的寬度。char類型一般是1位元組。而wchar_t類型跟編譯器與作業系統有關,Windows平台下一般2位元組,Linux平台下一般4位元組。

2. “len<?>=”右側的數字是字元個數。用char類型,一個漢字的GB2312編碼是2個字元,一個漢字的UTF-8編碼一般是3個字元。而對于wchar_t類型,一個漢字一般是1個字元。

3. “str=”右側的是所顯示的字元串。

4. “//”右側用于顯示每一個字元的值。

  需要測試這些方面——

1. 分别測試不同作業系統下的多種編譯器。

2. 無簽名的UTF-8與帶簽名的UTF-8。UTF-8存儲方案分别有兩種,一是無簽名的UTF-8,另一是帶簽名的UTF-8,這兩種方案的差別是——是否存在簽名字元(BOM)。

3. 執行字元集。VC2010增加了“#pragma execution_character_set("utf-8")”,訓示char的執行字元集是UTF-8編碼。

  根據上面的要求,制定好了測試項目,分别有Window平台下的測試與Linux平台下的測試。

  Window平台下的測試有——

[VC6, noBOM]:VC6.0 sp1,源碼使用無簽名的UTF-8編碼。

[VC6, BOM]:VC6.0 sp1,源碼使用帶簽名的UTF-8編碼。

[VC2003, noBOM]:VC2003 sp1,源碼使用無簽名的UTF-8編碼。

[VC2003, BOM]:VC2003 sp1,源碼使用帶簽名的UTF-8編碼。

[VC2005, noBOM]:VC2005 sp1,源碼使用無簽名的UTF-8編碼。

[VC2005, BOM]:VC2005 sp1,源碼使用帶簽名的UTF-8編碼。

[VC2010, noBOM]:VC2010 sp1,源碼使用無簽名的UTF-8編碼。

[VC2010, BOM]:VC2010 sp1,源碼使用帶簽名的UTF-8編碼。

[VC2010, noBOM, execution_character_set]:VC2010 sp1,源碼使用無簽名的UTF-8編碼,并使用“#pragma execution_character_set("utf-8")”。

[VC2010, BOM, execution_character_set]:VC2010 sp1,源碼使用帶簽名的UTF-8編碼,并使用“#pragma execution_character_set("utf-8")”。

[BCB6, noBOM]:Borland C++ Builder 6.0,源碼使用無簽名的UTF-8編碼。

[BCB6, BOM]:Borland C++ Builder 6.0,源碼使用帶簽名的UTF-8編碼。

[gcc(mingw), noBOM]:MinGW中的GCC 4.6.2,源碼使用無簽名的UTF-8編碼。

[gcc(mingw), BOM]:MinGW中的GCC 4.6.2,源碼使用帶簽名的UTF-8編碼。

  Linux平台下的測試有——

[gcc(fedora), noBOM, chs]:Fedora 17自帶的GCC 4.7.0,源碼使用無簽名的UTF-8編碼,系統語言設為“簡體中文”。

[gcc(fedora), BOM, chs]:Fedora 17自帶的GCC 4.7.0,源碼使用帶簽名的UTF-8編碼,系統語言設為“簡體中文”。

[gcc(fedora), noBOM, eng]:Fedora 17自帶的GCC 4.7.0,源碼使用無簽名的UTF-8編碼,系統語言設為“英語”。

[gcc(fedora), BOM, eng]:Fedora 17自帶的GCC 4.7.0,源碼使用帶簽名的UTF-8編碼,系統語言設為“英語”。

  測試結果彙總如下(分号“;”後的是我寫的注釋)——

[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析
[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析
[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析
[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析
[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析
[C/C++] 各種C/C++編譯器對UTF-8源碼檔案的相容性測試(VC、GCC、BCB)一、測試程式 二、測試結果 三、結果分析

  觀察測試結果,我們首先可以發現以下幾點——  

VC6和BCB6都無法編譯帶簽名UTF-8編碼的代碼檔案,它們會将簽名字元(BOM)當做錯誤的語句。

VC6無法識别“\u”轉義符。

VC2003無法識别UTF-8編碼的char。

  Windows下的測試以VC2010最為典型,以此為例來講解。

  在編譯過程中,處理字元串時會涉及下面兩種字元集——

源碼字元集(the source character set):源碼檔案是使用何種編碼儲存的。

執行字元集(the execution character set):可執行程式内儲存的是何種編碼。

  要想使程式不會亂碼,必須滿足——

1) 編譯器準确識别了源碼字元集,進而得到正确的字元串資料。

2) 運作環境的編碼與執行字元集相同。運作環境的編碼可通過setlocale函數來配置,“setlocale(LC_ALL, "")”表示使用系統預設編碼。對于簡體中文Windows來說一般是GB2312,如果執行字元集相同,那就能正常顯示,否則會亂碼。

  VC2010是這樣處理的——

源碼字元集:如果有簽名字元,就按它的編碼來解析;否則使用本地Locale字元集。

執行字元集:對于char類型,如果有“#pragma execution_character_set”,就按它的編碼來存儲字元串;否則使用本地Locale字元集。對于wchar_t類型,總是使用UTF-16編碼。

  當源碼使用帶簽名的UTF-8編碼時,VC2010能正确的識别源碼字元集是UTF-8。然後因沒有“#pragma execution_character_set”,執行字元集是本地Locale字元集——

[VC2010, BOM]

len<1>=5,str=一字A // D2 BB D7 D6 41 ; 因帶有BOM,編譯器正确的識别了字元串,并将其存儲為GB2312字元串。

len<2>=3,str=一字W // 4E00 5B57 0057 ; 因帶有BOM,編譯器正确的識别了字元串,并将其存儲為UTF-16字元串。

  當源碼使用無簽名的UTF-8編碼時,VS2010因找不到簽名字元,源碼字元集被誤認為是本地Locale字元集。然後因沒有“#pragma execution_character_set”,執行字元集是本地Locale字元集——

[VC2010, noBOM]

len<1>=6,str=一瀛桝 // D2 BB E5 AD 97 41 ; “字A”的UTF-8編碼為“E5 AD 97 41”,編譯器将它們識别為GB2312編碼的“瀛桝”,并将其存儲為GB2312字元串。

len<2>=3,str=一瀛梂 // 4E00 701B 6882 ; “字W”的UTF-8編碼為“E5 AD 97 57”,編譯器将它們識别為GB2312編碼的“瀛梂”,并将其存儲為UTF-16字元串。

  當使用“#pragma execution_character_set("utf-8")”配置了執行字元集為UTF-8後,情況變得更複雜了。我們先看看VC2010能正确識别源碼字元集的帶簽名檔案——

[VC2010, BOM, execution_character_set]

len<1>=6,str=一瀛桝 // D2 BB E5 AD 97 41 ; “\u4e00”被識别為“一”,并存儲為GB2312編碼“D2 BB”。“字A”的UTF-8編碼為“E5 AD 97 41”,編譯器正确的将其存儲為UTF-8編碼。但顯示時系統預設是 GB2312 編碼。

  再看看無簽名時的情況。VS2010因找不到簽名字元,源碼字元集被誤認為是本地Locale字元集,即誤将UTF-8識别為GB2312。然後根據執行字元集,又轉換編碼為UTF-8進行存儲。最後在運作時因預設編碼是GB2312,再次誤将UTF-8識别為GB2312——

[VC2010, noBOM, execution_character_set]

len<1>=8,str=一鐎涙 // D2 BB E7 80 9B E6 A1 9D ; “\u4e00”被識别為“一”,并存儲為GB2312編碼“D2 BB”。“字A”的UTF-8編碼為“E5 AD 97 41”,編譯器将它們識别為GB2312編碼的“瀛桝”,并存儲為UTF-8編碼的“E7 80 9B E6 A1 9D”。但顯示時系統預設是 GB2312 編碼。

len<2>=3,str=一瀛梂 // 4E00 701B 6882

  從上面這2個例子中,發現VC2010存在一個Bug——“#pragma execution_character_set”對“\u”轉義字元無效,“\u”轉義字元總是使用本地Locale字元集,而不是執行字元集。

  GCC的源碼字元集與執行字元集預設是UTF-8編碼,這是因為現在的Linux系統大多使用UTF-8編碼。就算調整了Linux系統語言後,隻是區域發生了變化,字元編碼依然是UTF-8。是以我們的程式在“簡體中文”與“英語”下,均能正确的顯示中文字元。

  MinGW中的GCC也是這樣的,源碼字元集與執行字元集預設是UTF-8編碼。但是簡體中文的Windows的預設編碼是GB2312,會将printf輸出UTF-8字元串誤認為是GB2312,造成亂碼。

  如果字元串常量中沒有非ASCII字元,建議源碼檔案使用無簽名的UTF-8編碼,這樣能支援早期的編譯器。

  如果字元串常量中含有非ASCII字元,建議源碼檔案使用帶簽名的UTF-8編碼,這樣能使大多數編譯器正确的處理源碼字元集。

  補充——

1. 注意條件僅是“字元串常量中沒有非ASCII字元”。如果是從外部檔案或其他途徑獲得非ASCII字元串,隻要選擇了合适的字元串函數,無簽名UTF-8編碼的源碼檔案也是能行的。

2. VC2010新增的“#pragma execution_character_set”用于明确要求UTF-8字元串的場合。由于Windows沒有UTF-8的locale,實用性較小,

參考文獻——

源碼下載下傳—— 

<a href="http://files.cnblogs.com/zyl910/testwchar.rar">http://files.cnblogs.com/zyl910/testwchar.rar</a>

繼續閱讀