詳談字符編碼[二]代碼頁和一個亂碼案例


        上一篇關於字符編碼的隨筆介紹了編碼,輸入碼,機內碼,字形碼,字形庫等概念。除此之外,還有一些其他的概念我們不得不了解,它們已經不屬於現在,但是卻時常影響着現在。代碼頁,正是這些有歷史感的概念之一。這篇博文帶你了解代碼頁和當前Windows對Unicode和ANSI編碼的支持情況,末尾分析了一個亂碼的案例,出於某知名軟件,你一定不想錯過。

Windows的默認編碼?

        偶爾在知乎看到這樣的問題:為什么中文Windows選擇GBK作為默認編碼?其實會有這樣的誤解也難怪,為什么這么說呢?大家都從控制台的Helloworld開始,后來想要輸出中文時自然先想到printf("你好,世界");運行發現真的出現了中文,仿佛英文和中文沒什么區別,世界很美好的感覺。但學習更多之后發現Windows下的strlen("你好,世界")的值竟然是10(嚴格說是MinGW下使用GBK作源編碼時或者使用VS時才是10),第一次感覺到了英文字母與漢字的區別,於是我們去尋找原因,終於得知GBK編碼之類的各種編碼,也知道了代碼頁這個令人疑惑的名詞。你好世界,世界卻是灰色的。

        實際上微軟早就聲明:“UTF-16Little-endian是Microsoft以及Windows操作系統中的編碼標准”。在Windows2000以前的操作系統上,內碼的編碼是和語言相關的(ANSI編碼)。那時候簡體中文版的Windows使用GBK,所有的中文的軟件中的字符串也都是GBK,所以在window上運行也不會亂碼。但是可想而至,這二者一旦不匹配就會出現亂碼。不同語言國家的Windows編碼都不一樣,因此微軟使用了代碼頁來解釋字符編碼,比如簡體中文版的Windows默認代碼頁就是GBK,這意味着默認使用GBK來解釋字符串,所以能顯示中文是必然的,顯示其他的語言(比如日語)是亂碼也是必然的。

        Windows2000之后(嚴格說是Windows NT 3.1之后)默認使用UTF-16作為編碼標准。這是什么意思呢,意思就是全世界的字符你都可以處理,如果安裝了相應的字體,你還可以顯示全部字符,如果安裝了相關輸入法你還可以輸入任意一種語言。但是哪些不是UTF-16編碼的程序還能在新平台下運行嗎?可以的,在這一方面微軟還是負責任的,畢竟當初是自己提出的代碼頁方案,不能把軟件開發商們都得罪了。所以直到今天(Windows 10)微軟都是兼容二者,但是提倡使用UTF-16。那么Windows的默認編碼是什么呢?事實是最好不要使用”默認編碼“這個詞,因為根本沒有什么默認的編碼(你可以決定使用任何一種編碼,只不過別人不認識而已),推薦使用官方的說法“編碼標准”,而且微軟的編碼標准是UTF-16L。

  之所以很多初學者有誤解,是因為一開始的程序基本都是控制台程序,而控制台的默認代碼頁確實是GBK。使用chcp命令可以查看當前代碼頁,可以看到回顯Active code page: 936,這正是代表GBK。可以使用命令“chcp 65001”切換到UTF-8。控制台為了兼容性默認代碼頁是936,不代表Windows的編碼標准是GBK,下面的試驗都在對話框上顯示,因為這是最簡單的檢驗GUI編碼方式的方法。

Windows對兩種機制的兼容

那么具體Windows是怎樣同時兼容二者:既支持UTF-16,又可以使用ANSI編碼的呢?使用一個MessageBox做一下試驗。

1 #include<windows.h>
2 int main() {
3     MessageBox(NULL, L"你好,世界", L"你好,世界", 0);
4     return 0;
5 }

效果是下面圖1這樣

     

           圖1                                             圖2

         圖3

        但大家都知道,這里是使用了(如圖2),調用MessageBox()實際上是調用了MessageBoxW()MessageBoxW()的參數是wchar_t類型的,wchar_t*的字符串字面量一般被實現成UTF-16編碼。與之對應的是MessageBoxA()MessageBoxA()接受的參數是char*類型的,char*的字符串字面量被實現成ANSI編碼。我說的字符串字面量被實現成某某編碼是什么意思呢?用圖片解釋一下(圖3),兩個字符串雖然都是“你好,世界”但是運行時的樣子完全不同。要強調機器只認識二進制,所以對機器來說這倆個字符串沒有任何相同點。我們如果就是調用MassageBoxA(),傳入char*會顯示什么呢。

#include<windows.h>
int main() {
    MessageBoxA(NULL, "你好,世界", "你好,世界", 0);
    return 0;
}

       很意外,結果竟然和圖1完全一樣。兩個完全不同的二進制串竟然顯示了相同的正確結果。其實這就是所謂的Windows兼容兩種編碼(UTF-16與ANSI編碼),雖然推薦使用UTF-16但是,使用GBK也能正常在Windows的GUI中顯示,但是你的應用程序已經被Windows歸類為“非Unicode程序”。這里大家可以打開控制面板--時鍾語言和區域--區域--管理,可以找到一個設置項:非Unicode程序的語言。可以選擇中文或者其他語言,那它有什么影響呢?你不妨嘗試一下改成日語或者韓語什么的,再運行第二段代碼,你就能看到亂碼了。但是除此之外幾乎感受不到影響,程序們還是正常的運行着,沒有出現亂碼,這說明現在的絕大多數程序都是使用的UTF-16編碼,所以這一個設置對他們根本沒有影響。

        但也並不絕對,在筆者把這一項設置修改為“日語(日本)”一周后(我已經忘了自己沒有改回來,因為確實沒有什么影響)看到一個奇怪的文件夾,他的名字是:ムクタラマツヤリ。是哪一個程序搞出了這樣的亂碼呢?文件夾名字原來是什么呢(不要指望這是日語,這只是日語字符組成的亂碼,不能看出含義)?下一節我們來分析這個例子,揭開這個還在使用ANSI編碼的程序的羞恥的面紗。

一個亂碼的例子分析

        日本的ANSI編碼是Shift_JIS,它是在有Unicode之前日本國內計算機的編碼方式。可想而知,之所以出現ムクタラマツヤリ這一段亂碼,就是因為我把非Unicode程序的語言設定成了日語(日本),所以導致某個想要用GBK字符串命名文件夾的程序創建了亂碼的名稱。現在能夠查到這些字符的Unicode編碼(復制粘貼后,他已經變成了Unicode),所以把Unicode轉換成的Shift_JIS二進制串解釋為GBK就得到文件夾本來的名字了。

ff fe 91 ff 78 ff 80 ff 97 ff 8f ff 82 ff 94 ff 98 ff<--這是UTF16小端編碼,開頭的0xfffe是BOM,不知道BOM是什么的可以查看詳解字符編碼[一]

d1 b8 c0 d7 cf c2 d4 d8<--這是對應的Shift_JIS

把上面的二進制翻譯成GBK就是答案:迅雷下載

我用的正是最新版本的迅雷:9.1.41.914。迅雷的一個小Bug就這樣被我發現了。

日志:在把非Unicode程序的語言改為日語后,只有一個MFC的上古程序和最新版的迅雷出現了亂碼,2017年10月9日。

 下一篇會介紹C/C++程序避免亂碼的方法並介紹怎樣在Java中處理UTF-16的代理對。喜歡請給個推薦,再見。

 


免責聲明!

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



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