#include <stdio.h>
#include <wchar.h>
int main(void) {
char str[] = "中文";
wchar_t wstr[] = L"中文";
printf("1:%s\n", str);
wprintf(L"2:%s\n", wstr);
return 0;
}
Windows平臺下VS2008輸出:
Windows平臺下MinGW輸出:
當加上setlocale函數設定後,
#include <stdio.h><span style="color: rgb(255, 0, 0); ">
</span>#include <locale.h>
#include <wchar.h>
int main(void) {
setlocale(LC_CTYPE, "");
char str[] = "中文";
wchar_t wstr[] = L"中文";
printf("1:%s\n", str);
wprintf(L"2:%s\n", wstr);
return 0;
}
輸出分別爲:
爲解其中各種紛亂的糾結,又讓我一個美好的下午就此悲劇= =.
=============================================================分割線
這檔子事還得從字符編碼說起.關於字符集和編碼的基礎知識,請看咱昨天寫的 字符集相關知識的簡單總結.
這裏涉及到一個字符在源代碼(文本)中,編譯好的二進制文件中,以及最後控制檯輸出編碼形式的區別.
首先,要明確一點:C(語言/程序)並不理解ANSI,UTF-8以及任何其他編碼.它只知道處理你給它的字符的二進制表示.
在簡體中文Windows下,默認的文本保存編碼是ANSI(即GBK);Linux下根據系統locale設定,一般應該是(zh_CN.UTF-8).(以下基於簡體中文Windows)
1)對於源文件中保存的"中文"這個字符串,VS2008看到的就是"0xd6d0"和"0xcec4"的形式(默認ANSI編碼得到).但編譯器纔不管是不是GBK神馬的,它就管那串數字.
區別,MinGW看到的是"0xe4b8ad"和"0xe69687"(gcc默認UTF-8).注意,用MinGW編譯的源文件中有中文寬字符必須保存爲UTF-8編碼.
2)然後,在二進制文件中的存儲形式,對傳統的字符串(char str[] = "中文";),編譯器什麼都不做,直接把那串數字(如"0xd6d0","0xcec4")搬過去塞進二進制文件.
但對於寬字符串(wchar_t wstr[] = L"中文";),編譯器會將其做轉換,轉換成Unicode編碼格式(在Windows是UTF-16,而Linux下是UTF-32).如"中文"的16位Unicode是"0x4e2d"和"0x6587",然後把這串轉換後的數字("0x4e2d","0x6587")塞進二進制文件中.(這裏VS和MinGW做的沒有區別)
這裏有點需要注意,編譯器必須知道你的源文件保存的編碼!如VS默認是ANSI編碼,如果你用UTF-8保存.c源文件去用VS打開看一定是亂碼.同理如果你用mingw編譯ANSI編碼保存的源文件,也會出錯!(但可以修改編譯選項解決,見文章末尾) 在本文這裏這個原因其實很好理解,因爲編譯器需要知道,如果它要將一個保存在文件中的字符轉成寬字符時,是從什麼編碼轉到Unicode.(可見上述VS是GBK->Unicode,而MinGW是UTF-8->Unicode)
來小結下"中""文"的3種編碼:
ANSI(GBK): 0xd6d0 0xcec4
UTF-8: 0xe4b8ad 0xe69687
Unicode: 0x4e2d 0x6587
到這裏,一切都還正常~
3)控制檯的輸出是問題關鍵!在簡體中文Windows下的控制檯顯示環境是ANSI編碼(代碼頁936, GBK),先明確這點.
對於傳統字符串輸出printf("%s\n", str);程序運行時,直接將二進制文件中存儲的那串數字丟進輸出流.到這裏,你該發現了吧:str保存在文件中是GBK,存儲在二進制文件中是GBK,到控制檯的輸出環境也是GBK!三者一致,自然輸出正常.(當然,如果你修改三者中任一的一個編碼,輸出結果都會不一樣)
但對於寬字符串呢,wprintf(L"%s\n", wstr);會怎麼做?wprintf會先二進制文件的Unicode編碼那串東西轉成本地區域編碼,然後丟進輸出流.哦!這本地區域編碼程序是怎麼得到就成關鍵中的關鍵了.這時咱們來看看setlocale這個函數吧.(看這裏看這裏>o<)
setlocale是用來程序運行時,設置當前的區域信息. 函數參數格式這裏就不介紹了,請看上面鏈接或Google.
值得注意是: 在所有C程序啓動前,locale的默認設置setlocale(LC_ALL,"C");會被執行.
那"C"是什麼環境呢?
The "C" locale is the minimal locale. It is a rather neutral locale which has the same settings across all systems and compilers, and therefore the exact results of a program using this locale are predictable. This is the locale used by default on all C programs.
其實這麼看咱也沒弄懂"C"具體是個啥區域環境,暫且鑑定爲是指那個只認128字符的編碼環境吧.(反正它不認中文= =)
所以,輸出時Unicode編碼默認轉成這個C環境編碼,然後丟進輸出流.而控制檯的顯示環境默認是GBK啊,這不就亂了嗎!所以亂碼啦~
解決辦法就是在程序中加上setlocale(LC_CTYPE, "");
LC_CTYPE表示C字符串相關的處理.而雙引號中是對應的locale字符串,如果什麼都不寫就從當前系統獲得默認的環境編碼.當然你也可以手動寫成setlocale(LC_CTYPE, "chs"); 一樣的.
這時候,程序輸出時將Unicode編碼的字串轉成系統的默認編碼(Windows下是ANSI),而Windows系統默認編碼一般都與控制檯環境編碼一致,OK~正常輸出了.
等等!在加了setlocale函數後的VS2008兩個"中文"都輸出正確了,而MinGW怎麼第一個卻還是亂碼"涓枃"?! 這是當然啦,忘了嗎?MinGW的源文件保存的編碼格式是UTF-8啊.並且程序將文本保存的UTF-8編碼(0xe4b8ad和0xe69687)塞進二進制文件中,輸出時也沒做轉換,又直接將那串UTF-8編碼丟進輸出流,在GBK環境的控制檯輸出,實際過程就相當於UTF-8==>GBK,要知道UTF-8與GBK可不兼容啊,這樣輸出顯示的結果註定是亂碼啊!
一切都清晰了是不是~
=============================================================又見分割線
在<淺談C中的wprintf和寬字符顯示>一文中,指出在Linux平臺下
wchar_t wstr[] = L"中文"; setlocale(LC_ALL, "zh_CN.UTF-8"); wprintf(L"%s/n",wstr);
這樣依然存在輸出亂碼問題.
這個問題的原因在於wprintf的格式化參數%s.應該呢,在標準C中,格式化參數%s表示普通字符串(char*),%ls表示寬字符串(wchar_t*)(貌似%S也可以表示寬字符串,但在C標準中已被拋棄了,迴避之)
所以Linux下應該用wprintf(L"%ls/n",wstr);纔可以正常輸出.
而wprintf(L"%s\n", wstr)的%s是將wstr當作多字節字符串,通過調用mbrtowc()函數轉換成Unicode編碼,再交給wprintf輸出. 可wstr本來就是Unicode編碼字符串,被當成MBCS編碼再轉換成Unicode,這多的一步處理使字符串的內容全亂了.
文章中也說明了Linux下輸出寬字符串,未必非要是wprintf,用printf("%ls\n", wstr)也可以. %ls將wstr當作寬字符,通過調用wcrtomb()函數轉換成多字節編碼(這裏是UTF-8),然後交給printf輸出.所以最後依然顯示正確.
而Windows下用%s和%ls都可以正確輸出.其區別我猜想應該是Windows下C運行庫(CRT)與Linux下的The GUN C Library(glibc)實現不同所致.
CRT太特立獨行,Linux下glibc的實現我覺得應該更符合C標準吧.
=============================================================再見分割線
最後附加:
之前提到過Windows下MinGW編譯的源文件有中文寬字符時,必須是UTF-8保存的(沒有中文的話就隨意啦).否則若源碼中有中文的寬字符變量時編譯會出錯: "converting to execution character set: Illegal byte sequence".(原因之前也說過了,是因爲要讓編譯器知道是從什麼編碼轉到Unicode的)
當然如果你執意要保存ANSI編碼,那麼可以指定gcc的輸入文件的編碼參數-finput-charset. (如-finput-charset=GBK)
同樣也能指定gcc的輸出編碼參數-fexec-charset. (如-fexec-charset=GBK 這樣之前那個"中文"顯示""涓枃"就也能得到正常輸出啦)