控制台程序的中文輸出亂碼問題(export LC_CTYPE=zh_CN.GBK,或者修改/etc/sysconfig/i18n為zh_CN.GBK。使用setlocale(LC_CTYPE, "");會使用默認辦法。編譯器會將源碼做轉換成Unicode格式,或者指定gcc的輸入文件的編碼參數-finput-charset=GBK。Linux下應該用wprintf(L"%ls/n",wstr))


今天發現用securecrt登陸時,gcc編譯出錯時會出現亂碼,但直接在主機的窗口界面下用Shell編譯卻沒有亂碼。查看了一下當時的錯誤描述,發現它的引號是中文引號,導致在SecureCRT中顯示出錯:

 before numeric constant

在網上查了一下,可以通過修改LC_CTYPE=zh_CN.GBK解決這個問題,具體的方法有兩個:

1. 通過export命令修改LC_CTYPE變量的值

    tianfang > export LC_CTYPE=zh_CN.GBK
    tianfang > gcc main.c
    main.c:1:1: error: expected identifier or '(' before numeric constant
    tianfang >

2. 修改/etc/sysconfig/language(大部分linux版本下這個文件叫/etc/sysconfig/i18n)中的變量設置,重新登陸后生效。

    #RC_LC_CTYPE=""
    RC_LC_CTYPE="zh_CN.GBK"

我個人比較推薦方法2。

 

 

#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> #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  這樣之前那個"中文"顯示""涓枃"就也能得到正常輸出啦)

 

參考:

淺談C中的wprintf和寬字符顯示

為什么printf可以打印中文,而wprintf卻一定要setlocale才能正確打印?

解決使用VC運行時庫函數wprintf和wcount顯示中文不正確的問題

為什么 wprintf 無法打印中文?

C標准庫的setlocale()用法筆記

Why printf() does not care of my locale settings ?

printf("%ls")

簡析MinGW編譯器以及vc使用wxWidgets的漢字問題

 

 

本文轉載自:http://www.cnblogs.com/dejavu/archive/2012/09/16/2687586.html

https://my.oschina.net/mickelfeng/blog/144484


免責聲明!

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



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