淺談C/C++編程中的字符編碼轉換

背景

在寫跨平臺的C/C++代碼過程中(本文的研究只限於C/C++範疇),經常會遇到中文字符串亂碼的問題。比如,同一個源碼,用MSVC編譯/運行能正常顯示中文字符串,但在linux下編譯/運行顯示中文字符串就亂碼。

導致這種現象的根源就在於字符集編碼不匹配導致,本文將探索隱藏在編程過程中鮮爲人知的字符集轉換問題,如果你徹底理解了以下幾個字符集的概念,以及編程過程中哪些因素會影響這些字符集,將有助於你從根源上解決亂碼問題。

  1. 源碼字符集:
    英文the source character set,是指源代碼文件是使用何種編碼字符集保存的。

  2. 執行字符集:
    英文the execution character set,是指源代碼經過編譯、鏈接後的可執行文件是使用何種編碼字符集保存的,程序實際執行時,內存中的字符串編碼就是執行字符集。

  3. 運行環境編碼:
    是指操作系統(或者當前控制檯環境)用於顯示文字的編碼字符集。

亂碼的根源

源代碼文件(源碼字符集)經過編譯/鏈接,生成可執行文件(執行字符集),最後程序運行於實際環境中(運行環境編碼)。在這過程中如果有字符集不匹配,最終就無法顯示預期的文字信息,甚至產生亂碼。

  1. 編譯器在編譯源代碼時,會將源碼字符集轉化爲執行字符集,如果編譯器不能正確識別源碼字符集,就得不到正確的字符串數據。

  2. 可執行文件在實際運行環境中執行時,爲了在控制檯(或者其他UI)上顯示出字符串,就要將執行字符集轉化爲運行環境的字符集。如果運行環境的字符集與執行字符集不同,也會導致亂碼。

總結起來,要想使程序不會亂碼,必須滿足:

  1. 編譯器準確識別了源碼字符集,從而得到正確的字符串數據(執行字符集)。

  2. 運行環境的編碼與執行字符集相同。

字符集

有關字符集的介紹,以下這篇文章講解的很好,大家先看這篇文章,標題是“字符集編碼與 C/C++ 源文件字符編譯亂彈”,鏈接地址:http://jimmee.iteye.com/blog/2165685

此處引用其幾個概念的定義:

  • C locale

ANSI 發佈的字符編碼標準,編碼空間 0x00-0x7F,佔用1個字節,上學時學的 C 語言書後面的字符表中就是它,因爲使用這個字符集中的字符就已經可以編寫 C 程序源代碼了,所以給這個字符集起一個 locale 名叫 C,所有實現的 C 語言運行時和系統運行時,都應該有這個 C locale,因爲它是所有字符集中最小的一個,設置爲其它 locale 時可能由於不存在而出錯,但設置 C 一定不會出錯,比如:當 Linux 的 LANG 配置出錯時,所有的 LC_* 變量就會被自動設置爲最小的 C locale。

  • 單字節字符集(SBCS - Single-Byte Character Set)

像ASCII、ISO-8859-1 這種用1個字節編碼的字符集,叫做單字節字符集(SBCS - Single-Byte Character Set)。

  • 多字節字符集(MBCS - Multi-Byte Character Set)

像GB2312,GBK,GB18030這種用1-2、4個不等字節編碼的字符集,叫做多字節字符集(MBCS - Multi-Byte Character Set)。

  • GB2312,GBK,GB18030

GB18030:最新漢字編碼字符集,向下兼容GBK,GB2312;
GBK:漢字擴展編碼,向下兼容GB2312, 幷包含BIG5(繁體)全部漢字;
GB2312:簡化漢字編碼字符集;

源碼字符集

不同工具新建的源碼文件編碼格式不同

源代碼都是由不同操作系統的不同編輯工具產生的,不同工具新建的源碼文件編碼格式不同,比如拿我電腦來說:

  1. 在Windows下用VS2010新建的源碼文件是GB2312編碼格式。
  2. 在Windows下用notepad++新建的源碼文件是UTF-8編碼格式。
  3. 在Linux下用VI新建的源碼文件是UTF-8格式。

用以下工具可以方便確認文件的編碼格式

Linux命令file:

# file main.c
main.c: UTF-8 Unicode (with BOM) C program text, with CRLF, LF line terminators

在Windows下,沒有便利的命令可用,可以使用各種編輯工具的“另存爲”間接查看,比如Microsoft Visual Studio 2010菜單“文件 > 高級保存選項”。或者,如果你的windows下有安裝git,可以打開git bash按linux的方式查看。或者,如果你是windows 10版本,也可以利用原生支持的Linux Bash命令行查看。

文件編碼格式轉換

Linux下可以使用iconv進行轉化,如

# iconv -f UTF-8 -t GB2312 main.c

Windows很多編輯工具的“另存爲”都有轉換編碼格式的選項。比如Microsoft Visual Studio 2010菜單“文件 > 高級保存選項”。

執行字符集

源碼編譯成可執行文件,源碼字符集會轉換成執行字符集,可執行文件中的字符串常量就是執行字符集,可以通過WinHex、hd 等16進制查看工具,對執行文件進行查看。可執行文件中的字符串常量字節流,跟程序運行起來內存中的字節流是一樣的。

先看下編譯器如何識別源碼文件,編譯過程中又是如何將源碼字符集轉化爲執行字符集的。拿Microsoft Visual Studio 2010和GCC做舉例。

MSVC(SP1)

  1. 識別“源碼字符集:
    源碼文件有BOM簽名的,就按BOM的編碼來解析源文件;否則使用本地Locale字符集解析源文件(隨系統設置而變)。

  2. 轉化“執行字符集”:
    對於char類型,如果有設置預處理選項“#pragma execution_character_set”,編譯源碼時,轉換爲預編譯所設定的執行字符集;否則使用本地Locale作爲執行字符集。對於wchar_t類型,總是使用UTF-16編碼。

注意:#pragma execution_character_set預處理指令是在Microsoft Visual Studio 2010 SP1以上纔有,Microsoft Visual Studio 2010要打上SP1補丁才支持。所以代碼要類似這樣寫:

#if _MSC_VER >= 1600  /* 1600 is Microsoft Visual Studio 2010 */
#pragma execution_character_set("utf-8")
#endif

GCC

GCC的源碼字符集與執行字符集默認都是UTF-8編碼,也就是說默認情況下GCC都是按UTF-8來解析源碼,編譯後的執行字符集也是UTF-8。當然GCC也提供改變默認情況的編譯選項(注意是編譯過程中的選項,不是鏈接過程)。

-finput-charset=charset 用於指定源碼字符集
-fexec-charset=charset 用於指定執行字符集

除了前兩個選項外,還有一個:

-fwide-exec-charset=charset

以下的測試程序,能佐證上面的觀點:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if _MSC_VER >= 1600  /* 1600 is Microsoft Visual Studio 2010 */
#pragma execution_character_set("utf-8")
#endif

int main(int argc, char *argv[])
{
    char *str = "123漢ABC";
    char *p;

    printf("%d |", strlen(str));
    for(p=str; *p; p++) {
        printf(" %.2X", (unsigned char)(*p));
    }
    printf(" | %s\n", str);

    return 0;
}

以下表格個字段含義解釋:

  • 源碼字符集:使用WinHex查看源碼中字符串常量字節流,使用Microsoft Visual Studio 2010菜單“文件 > 高級保存選項”來轉換源碼字符集。

  • 執行字符集:使用WinHex查看可執行文件中字符串常量的字節流。

  • 是否設置windows預編譯選項:#pragma execution_character_set(“utf-8”)

  • 是否設置Linux編譯選項:-finput-charset和-fexec-charse

  • 紅色顯示的就是“漢”的編碼:“漢”的GBK編碼爲BA BA,UTF-8編碼爲E6 B1 89


Microsoft Visual Studio 2010編譯器測試數據:
這裏寫圖片描述


GCC(版本gcc version 4.4.5)測試數據:
這裏寫圖片描述

運行環境編碼

如果運行環境編碼(字符集)與執行字符集不同,也會導致亂碼,從上面的測試數據中也能看出來這點。爲了顯示正確的字符,可以通過修改運行環境編碼(字符集),讓其跟執行字符集保持一致即可。

Windows控制檯運行環境編碼

要查看windows控制檯當前的運行環境編碼,可以在cmd.exe輸入chcp,或者在cmd.exe標題欄右鍵屬性查看,顯示結果類似“活動的代碼頁: 936”。

如果要修改編碼,可在cmd.exe輸入CHCP [nnn]回車,其中nnn指定的是代碼頁的編號。比如將控制檯的字符集改爲UTF-8:chcp 65001

代碼頁(Code Page)是字符集編碼的別名,也有人稱”內碼錶”。如936是簡體中文(GBK)的代碼頁編號,65001是UTF8的代碼頁編號。

Linux終端運行環境編碼

我們知道linux系統有六個字符終端(tty1~tty6)和一個圖形桌面(GUI窗口,tty7),從圖形桌面切換到字符終端,只需按快捷鍵CTRL+ALT+F1,或CTRL+ALT+F2……CTRL+ALT+F6。要切換回圖形桌面,只需按快捷鍵CTRL+ALT+F7。

“終端”在歷史早期是屬於硬件設備,我們現在說的linux終端(Terminal ),主要包括兩種類型:“虛擬終端”和“模擬終端”。

  • 虛擬終端:指的是字符終端tty1~tty6;

  • 模擬終端:指的是圖形桌面中的終端,我更喜歡它的另外一個名稱“終端模擬程序(Terminal Emulation Program)”或者“終端模擬器(Terminal Emulator)”,它是一個程序,明顯的特徵是帶有窗口,如Ubuntu默認的終端模擬器GNOME,還有我們平常在Windows系統中遠程登錄(SHH/Telnet等)到linux中用的終端也是終端模擬器。

網上很多資料說linux終端要支持中文,只要修改locale環境變量即可,但這些方法對我都不奏效,不管是在字符終端,還是圖形桌面終端,修改locale不能讓我上面的測試程序在終端中打印出GBK編碼的“漢”字(打印出來都是亂碼)。可能是我我研究的還不夠深入,不過我使用以下這些方法也能讓我打印出GBK編碼的“漢”字,不管這種方法主不主流,那不是本文討論的範疇,我要強調的一件事是:運行環境編碼(字符集)與執行字符集不同,也會導致亂碼,如果兩個字符集一樣,就不會亂碼。

先說說圖形桌面的終端模擬器,終端模擬器要支持中文比較簡單,只要在窗口標題欄菜單中設定字符編碼即可。如Ubuntu的圖形桌面默認的終端是,GNOME 桌面的終端模擬器,要改變其字符編碼格式,在菜單“終端 > 設定字符編碼”中改變,如下圖所示。Windows下遠程登錄的終端模擬器也是如此修改,畢竟他們都是帶有窗口的程序而已,修改起來簡單。


這裏寫圖片描述

tty1~tty6字符終端要顯示中文就比較麻煩了,幾乎任何一種linux發行版,在tty1~tty6字符終端中都無法正常顯示中文(中文會顯示成亂碼),即使你在圖形桌面(tty7)中已經安裝中文語言支持(已經能夠在終端模擬器中顯示中文),也是沒個卵用。

要在tty1~tty6中顯示中文,就得裝一些中文化接口的軟件,如cce、zhcon或fbterm等。

zhcon是一個工作在Linux控制檯下的多內碼中文平臺。 它能夠在控制檯上顯示簡體中文、繁體中文、日文、韓文等雙字節字符。它的項目主頁是:http://sourceforge.net/projects/zhcon

下面就拿zhcon舉例(我的環境是Ubuntu環)。

安裝
# sudo apt-get install zhcon

啓動
# zhcon


這裏寫圖片描述

不帶參數運行zhcon,默認的編碼是gb2312,要utf8編碼就要帶參數:

# zhcon --utf8 --drv=auto
發佈了90 篇原創文章 · 獲贊 351 · 訪問量 150萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章