今天發現用securecrt登錄時,gcc編譯出錯時會出現亂碼,但直接在主機的窗口界面下用Shell編譯卻沒有亂碼。查看了一下當時的錯誤描述,發現它的引號是中文引號,致使在SecureCRT中顯示出錯:html
before numeric constantlinux
在網上查了一下,能夠經過修改LC_CTYPE=zh_CN.GBK解決這個問題,具體的方法有兩個:ide
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 >ui
2. 修改/etc/sysconfig/language(大部分linux版本下這個文件叫/etc/sysconfig/i18n)中的變量設置,從新登錄後生效。this
#RC_LC_CTYPE=""
RC_LC_CTYPE="zh_CN.GBK"編碼
我我的比較推薦方法2。spa
#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輸出:.net
Windows平臺下MinGW輸出:3d
當加上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 這樣以前那個"中文"顯示""涓枃"就也能獲得正常輸出啦)
參考:
爲何printf能夠打印中文,而wprintf卻必定要setlocale才能正確打印?
解決使用VC運行時庫函數wprintf和wcount顯示中文不正確的問題
Why printf() does not care of my locale settings ?
簡析MinGW編譯器以及vc使用wxWidgets的漢字問題