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


如果系統預設編碼是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編碼,系統語言設為“英語”。
測試結果彙總如下(分号“;”後的是我寫的注釋)——


觀察測試結果,我們首先可以發現以下幾點——
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>