一、一個(gè)正常的C程序
第一步,在Windows 10環(huán)境下,使用Notepad++編寫(xiě)如下源代碼,并保存到文件main.c當(dāng)中。
這段代碼的意思,是在控制臺(tái)輸出兩個(gè)字符串,一個(gè)是China,另一個(gè)是中國(guó)。
第二步,使用如下gcc(gcc 8.2.0,下同)命令編譯上述源代碼,生成可執(zhí)行文件main.exe。
gcc main.c -o main.exe
第三步,運(yùn)行剛剛生成的main.exe,輸出結(jié)果如下。
上述程序代碼和輸出結(jié)果如預(yù)期一般,一切正常。下面我們?cè)賮?lái)看一個(gè)程序。
二、一個(gè)“不正?!钡腃程序
第一步,在Windows 10環(huán)境下,使用Notepad++編寫(xiě)如下源代碼,并保存到文件main.utf8.c當(dāng)中。
這段代碼跟上面的程序代碼完全相同。但是,跟上一個(gè)程序不同的是,這次保存文件main.utf8.c時(shí)選擇的源文件字符集編碼格式是UTF-8。
第二步,使用如下gcc命令編譯上述源代碼,生成可執(zhí)行文件main.utf8.exe。
gcc main.utf8.c -o main.utf8.exe
第三步,運(yùn)行剛剛生成的main.utf8.exe,輸出結(jié)果如下。
可以看到,這次的輸出與上一個(gè)程序的輸出不同。字符串China如預(yù)期一樣正常輸出了,但是中國(guó)這兩個(gè)字并沒(méi)有被輸出,取而代之的是輸出了涓浗這幾個(gè)奇怪的字符。
同樣的源代碼,怎么源文件的字符編碼不同,輸出結(jié)果就不同了呢?
三、源文件的字符編碼
既然是源文件編碼不同導(dǎo)致了輸出結(jié)果不同,那就首先來(lái)看一下兩個(gè)源文件的內(nèi)容。
我自己寫(xiě)了一個(gè)以二進(jìn)制方式讀取文本文件的函數(shù),代碼如下。這段代碼的意思就是將指定文件以二進(jìn)制方式讀入內(nèi)存,然后,逐個(gè)字節(jié)輸出它的十六進(jìn)制表示以及對(duì)應(yīng)的字符。當(dāng)然,如果你喜歡用其他的工具,那也很好。
void readFileWithBinary(const char *filePath) FILE *pFile = fopen(filePath, "rb"); printf("Failed to open file:%s\n", filePath); unsigned char buffer[BUFFER_LEN]; readCount = fread(buffer, sizeof(unsigned char), BUFFER_LEN, pFile); for (i = 0; i < readCount; i++) unsigned char c = buffer[i];
需要說(shuō)明的是,在簡(jiǎn)體中文環(huán)境下,Windows 10默認(rèn)的字符集編碼是GBK,對(duì)應(yīng)的代碼頁(yè)是936。也就是說(shuō)第一個(gè)源文件main.c的編碼格式是GBK。
用這個(gè)二進(jìn)制讀取程序讀取上面的源代碼文件main.c,可以看到其中的部分輸出如下。
) 9() 70(p) 75(u) 74(t) 73(s) 28(() 22(") 43(C) 68(h) 69(i) 6E(n) 61(a) 22(") 29()) 3B(;) D( ) 9() 70(p) 75(u) 74(t) 73(s) 28(() 22(") D6(? D0(? B9(? FA(? 22(") 29()) 3B(;) D(
可以看到,在main.c文件中,China這個(gè)字符串的5個(gè)字母及其對(duì)應(yīng)的編碼的十六進(jìn)制表示如下表。
而中國(guó)這兩個(gè)字的存儲(chǔ)對(duì)應(yīng)的是以下4個(gè)連續(xù)字節(jié):
如果我們查閱GBK編碼表,可以確認(rèn),D6D0對(duì)應(yīng)的是中字,B9FA對(duì)應(yīng)的是國(guó)字。
然后用上面同樣的二進(jìn)制讀取程序讀取另一個(gè)源文件main.utf8.c,可以看到其中的部分輸出如下。
) 9() 70(p) 75(u) 74(t) 73(s) 28(() 22(") 43(C) 68(h) 69(i) 6E(n) 61(a) 22(") 29()) 3B(;) D( ) 9() 70(p) 75(u) 74(t) 73(s) 28(() 22(") E4(? B8(? AD(? E5(? 9B(? BD(? 22(") 29()) 3B(;) D(
可以看到,在以UTF-8編碼保存的main.utf8.c中,China這個(gè)字符串的5個(gè)字母及其對(duì)應(yīng)的編碼的十六進(jìn)制表示如下表??梢园l(fā)現(xiàn)它與main.c當(dāng)中的China的編碼完全一樣。
而在main.utf8.c文件當(dāng)中,中國(guó)這兩個(gè)字的存儲(chǔ)對(duì)應(yīng)的是以下6個(gè)連續(xù)字節(jié):
如果我們查閱UTF-8編碼表,可以確認(rèn),前三個(gè)字節(jié)的組合編碼E4B8AD對(duì)應(yīng)的是中字,后三個(gè)字節(jié)的組合編碼E59BBD對(duì)應(yīng)的是國(guó)字。
對(duì)比可以發(fā)現(xiàn),中國(guó)這兩個(gè)字在GBK編碼的main.c文件當(dāng)中和UTF-8編碼的main.utf8.c文件當(dāng)中的編碼是不同的。
四、可執(zhí)行文件的字符編碼
了解了源文件存儲(chǔ)的編碼的不同,我們還需要了解不同字符編碼的源文件對(duì)于GCC編譯器生成的可執(zhí)行文件有什么影響。如果不了解這一點(diǎn),那就不能從整體上了解源文件字符編碼是如何影響可執(zhí)行文件的輸出結(jié)果的。
現(xiàn)在,仍然使用上面的那個(gè)二進(jìn)制讀取程序來(lái)讀取GBK編碼的main.c源文件對(duì)應(yīng)的可執(zhí)行文件main.exe,其中有一部分輸出如下。
...43(C) 68(h) 69(i) 6E(n) 61(a) 0( ) D6(? D0(? B9(? FA(? 0( ) 0( ) ...
對(duì)比源文件main.c的內(nèi)容,可以看到main.exe文件里面,China這5個(gè)字符的存儲(chǔ)是使用了連續(xù)的5個(gè)字節(jié),而且跟main.c文件當(dāng)中的5個(gè)連續(xù)字節(jié)完全一樣。中國(guó)這2個(gè)漢字的存儲(chǔ)是使用了連續(xù)的4個(gè)字節(jié)(D6 D0 D9 FA),而且跟main.c文件當(dāng)中的4個(gè)連續(xù)字節(jié)完全一樣。
同樣可以讀取UTF-8編碼的main.utf8.c源文件對(duì)應(yīng)的可執(zhí)行文件main.utf8.exe,其中有一部分輸出如下。
...43(C) 68(h) 69(i) 6E(n) 61(a) 0( ) E4(? B8(? AD(? E5(? 9B(? BD(? 0( ) 0( ) 0( ) 0( )...
對(duì)比源文件main.utf8.c的內(nèi)容,可以看到main.utf8.exe文件里面,China這5個(gè)字符的存儲(chǔ)是使用了連續(xù)的5個(gè)字節(jié),而且跟main.utf8.c文件當(dāng)中的5個(gè)連續(xù)字節(jié)完全一樣。中國(guó)這2個(gè)漢字的存儲(chǔ)是使用了連續(xù)的6個(gè)字節(jié)(E4 B8 AD E5 9B BD),而且跟main.utf8.c文件當(dāng)中的6個(gè)連續(xù)字節(jié)完全一樣。
我們可以發(fā)現(xiàn),無(wú)論源文件的編碼是什么,通過(guò)前面的GCC編譯過(guò)程產(chǎn)生的可執(zhí)行文件(main.exe和main.utf8.exe)當(dāng)中,存儲(chǔ)的字符的編碼和生成他們的源文件(main.c和main.utf8.c)當(dāng)中存儲(chǔ)的字符的編碼并沒(méi)有區(qū)別。換句話說(shuō),GCC在編譯過(guò)程中直接拷貝了源文件當(dāng)中的那一串字節(jié)。當(dāng)執(zhí)行puts等控制臺(tái)輸出的時(shí)候,程序(main.exe和main.utf8.exe)直接把那一串連續(xù)字節(jié)輸出到標(biāo)準(zhǔn)輸出stdout。至于怎么把這一串編碼轉(zhuǎn)換為相應(yīng)的輸出字符,那就跟操作系統(tǒng)和控制臺(tái)程序有關(guān)系了。
五、那個(gè)“不正?!钡某绦蚴窃趺椿厥?/h2>
再來(lái)看一下那個(gè)“不正常”的輸出。
它不正常的地方是原本應(yīng)該希望它輸出中國(guó)這兩個(gè)字,現(xiàn)在卻輸出了涓浗這三個(gè)字符。查一下GBK編碼表就會(huì)發(fā)現(xiàn),這三個(gè)字符及其對(duì)應(yīng)的GBK編碼十六進(jìn)制表示如下表。
如果把它們連在一起,剛好就是中國(guó)這兩個(gè)字的UTF-8編碼的值(連續(xù)6個(gè)字節(jié):E4 B8 AD E5 9B BD)。所以那個(gè)“不正?!钡恼嫦嗑褪牵嚎刂婆_(tái)把main.utf8.exe輸出的這6個(gè)字節(jié)當(dāng)作是GBK字符流來(lái)解釋了。那么控制臺(tái)為什么會(huì)按照GBK來(lái)處理那6個(gè)字節(jié)呢?如果我們看一下控制臺(tái)的屬性,會(huì)發(fā)現(xiàn)它的當(dāng)前代碼頁(yè)這一項(xiàng)的內(nèi)容是:936(ANSI/OEM-簡(jiǎn)體中文GBK)。這也是Windows 10簡(jiǎn)體中文版系統(tǒng)的ANSI字符集。
現(xiàn)在我們可以看一下改變控制臺(tái)窗口的代碼頁(yè)是否能讓main.utf8.exe輸出預(yù)期的結(jié)果。在控制臺(tái)窗口運(yùn)行以下命令將該窗口的代碼頁(yè)修改為65001,也就是UTF-8字符集。
chcp 65001
現(xiàn)在再來(lái)運(yùn)行main.utf8.exe,可以得到如下輸出。
搞定?。?!
不過(guò),如果我們現(xiàn)在運(yùn)行main.exe的話,會(huì)得到如下“不正常”的輸出。
可以看到,й這個(gè)奇怪的字符替代了原來(lái)936代碼頁(yè)情況下輸出的中國(guó)兩個(gè)字。而й這個(gè)字符的UTF-8編碼恰好是D0B9,也就是中國(guó)這兩個(gè)字的GBK(代碼頁(yè)936)編碼。
這也再次說(shuō)明了控制臺(tái)代碼頁(yè)(字符集)對(duì)程序的字符輸出的影響。
六、總結(jié)
以上內(nèi)容主要提出了C語(yǔ)言程序源文件編碼格式不同對(duì)字符輸出的影響,通過(guò)研究源文件編碼、可執(zhí)行文件編碼和控制臺(tái)程序的編碼設(shè)置,對(duì)于影響字符輸出的這幾個(gè)相關(guān)因素有了初步的了解。然而,以上分析還不算究竟,所涉及的場(chǎng)景還不夠全面,對(duì)于其他的影響因素還沒(méi)有涵蓋。我將在接下來(lái)的幾篇文章中陸續(xù)進(jìn)行分析。
以上內(nèi)容,難免有錯(cuò)誤之處。請(qǐng)大家不吝賜教!
|