C/C++對Unicode編碼的處理

http://blog.chinaunix.net/uid-23414687-id-2425175.html


 MultiByteToWideChar(CP_ACP,0,p,-1,tUni,1);//調用windows的API函數將中文內碼轉換成  UNICODE碼
這個函數的具體參數可以查詢MSDN,其功能爲將多字符轉換爲寬字符,還對應有一個將寬字節轉換爲多字節的函數。
   outUnic[j++]=tUni[0];//tUni中爲轉換好的中文Unicode內碼

http://www.ibm.com/developerworks/cn/linux/l-cn-ccppglb/

編碼轉換方法

一般的編碼轉換,直接做映射的不太可能,需要比較多的工作量,大多情況下還是選擇 Unicode 作爲轉換的中介。

使用庫函數

如前文所說,JAVA 的 String 對象是以 Unicode 編碼存在的,所以 JAVA 程序員主要關心的是讀入時判斷字節流的編碼,從而確保可以正確的轉化爲 Unicode 編碼;相比之下,C/C++ 將外部文件讀出的數據存爲字符數組、或者是 string 類型;而 wstring 纔是符合 Unicode 編碼的雙字節數組。一般常用的方法是 C 標準庫的 wcstombs、mbstowcs 函數,和 windows API 的 MultiByteToWideChar 與 WideCharToMultiByte 函數來完成向 Unicode 的轉入和轉出。

這裏以 MBs2WCs 函數的實現說明 GB2312 向 Unicode 的轉換的主要過程:

清單 1. 多字節字符串向寬字節字符串轉換
wchar_t * MBs2WCs(const char* pszSrc){ 
    wchar_t* pwcs = NULL; 
    intsize = 0; 
    #ifdefined(_linux_) 
        setlocale(LC_ALL, "zh_CN.GB2312"); 
        size = mbstowcs(NULL,pszSrc,0); 
        pwcs = new wchar_t[size+1]; 
        size = mbstowcs(pwcs, pszSrc, size+1); 
        pwcs[size] = 0; 
    #else
        size = MultiByteToWideChar(20936, 0, pszSrc, -1, 0, 0); 
        if(size <= 0) 
            returnNULL; 
        pwcs = new wchar_t[size]; 
        MultiByteToWideChar(20936, 0, pszSrc, -1, pwcs, size); 
    #endif 
    returnpwcs; 
 }

相應的,WCs2MBs 可以將寬字符串轉化爲字節流。

清單 2. 寬字節字符串向多字節字符串轉換
char* WCs2MBs(const wchar_t * wcharStr){ 
    char* str = NULL; 
    intsize = 0; 
    #ifdefined(_linux_) 
        setlocale(LC_ALL, "zh_CN.UTF8"); 
        size = wcstombs( NULL, wcharStr, 0); 
        str = new char[size + 1]; 
        wcstombs( str, wcharStr, size); 
        str[size] = '\0'; 
    #else
        size = WideCharToMultiByte( CP_UTF8, 0, wcharStr, -1, NULL, NULL, NULL, NULL ); 
        str = new char[size]; 
        WideCharToMultiByte( CP_UTF8, 0, wcharStr, -1, str, size, NULL, NULL ); 
    #endif 
    returnstr; 
 }

Linux 的 setlocale 的具體使用可以參閱有 C/C++ 文檔,它關係到文字、貨幣單位、時間等很多格式問題。Windows 相關的代碼中 20936 和宏定義 CP_UTF8 是 GB2312 編碼對應的的 Code Page[ 類似的 Code Page 參數可以從 MSDN的 Encoding Class 有關信息中獲得 ]。

這裏需要特別指出的是 setlocale 的第二個參數,Linux 和 Windows 是不同的:

  • 1. 筆者在 Eclipse CDT + MinGW 下使用 [country].[charset](如 zh_CN.gb2312 或 zh_CN.UTF8)的格式並不能通過編碼轉換測試,但可以使用 Code Page,即可以寫成 setlocale(LC_ALL, ".20936") 這樣的代碼。這說明,這個參數與編譯器無關,而與系統定義有關,而不同操作系統對於已安裝字符集的定義是不同的。
  • 2. Linux 系統下可以參見 /usr/lib/locale/ 路徑,系統所支持的 locale 都在這裏。轉換成 UTF8 時,並不需要 [country] 部分一定是 zh_CN,en_US.UTF8 也可以正常轉換。

另外,標準 C 和 Win32 API 函數返回值是不同的,標準 C 返回的 wchar_t 數組或者是 char 數組都沒有字符串結束符,需要手動賦值,所以 Linux 部分的代碼要有區別對待。

最後,還要注意應當在調用這兩個函數後釋放分配的空間。如果將 MBs2WCs 和 WCs2MBs 的返回值分別轉化爲 wstring 和 string,就可以在它們函數體內做 delete,這裏爲了代碼簡明,故而省略,但請讀者別忘記。 


http://hi.baidu.com/ytwirvexplbbtvq/item/0ed1fe3b2c4c7a8ac2cf2922


字符集編碼與 C/C++ 源文件字符編譯亂彈(轉)

最近在看國際化編程 (i18ninternationalization) 的東西,也弄清楚了點字符集有關的一些問題,其實網上的一些牛人已經將字符集、Unicode 等相關的問題說的很清楚了,我在這裏引用他們的總結並自己小結一下心得,並且實驗一下在編譯時,源代碼自身的字符集與編譯生成工具之間的問題。

locale與字符集

locale,中文有時翻譯成“現場”,還不如叫英文的locale好,它的意思是“一套和地域有關的習慣而形成的程序運行上下文”,它由很多方面 (category) 組成,比如:某個地區的人們習慣怎樣表示他們的貨幣金額 (LC_MONETARY) ,是用 "$100",還是用 "¥100";習慣怎麼表示十進制多位數 (LC_NUMERIC) ,是每一千位進行分隔 "100,000",還是每一萬位進行分隔 "10,0000";習慣怎麼表示日期時間 (LC_TIME) ,是日-月-年的方式 "30-01-1999",還是年-月-日的方式 "1999-01-30",等等還有其它一些方面,不過其中我們最關心的是一個叫 LC_CTYPE 的,CTYPE 的含義大概是:Character Type(字符類型),它表示某個地區的字符用哪個字符集進行編碼。還有LC_ALL,它是其它所有方面的並集。

C 標準庫中設置 locale 的函數是:setlocale(),MSDN VC10 參考:Language and Country/Region Strings

字符集(Character-Set)按照發明順序和繼承關係,有以下常用的幾種:

  1. ASCII

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

  2. ISO-8859-1

    ISO 發佈的字符編碼標準,又稱 Latin-1 字符集,編碼空間 0x00-0xFF,佔用1個字節,可以編碼大多數的西歐地區語言。參考:ISO IEC 8859-1

  3. GB2312,GBK,GB18030

    GB 系列是由中國國標局發佈的字符編碼方案(其中 GBK 不是正式標準),後期發佈的版本兼容之前的,是之前的超集。

    參考:中文的幾個編碼 by blade2001

    • GB2312

      爲1-2字節變長編碼,漢字區中編碼 6763 個字符。

    • GBK

      是微軟對 GB2312 的擴展,後由國標局作爲指導性標準,爲1-2字節變長編碼,編碼 21886 個字符,分爲漢字區和圖形符號區。漢字區編碼 21003 個字符,支持CJK漢字(簡體、繁體、常用日韓文),Windows 代碼頁爲 CP936。

    • GB18030

      爲1-2-4字節變長編碼,漢字區編碼 27484 個字符,支持CJK漢字、常用藏文、蒙文、維吾爾文等,Windows 代碼頁爲 CP54936。

上面的所有編碼都將ASCII作爲自己的子集實現,所以這些字符集又叫做本地化的(Native)ANSI 字符集。

一般在程序中爲了支持國際化,在程序初始化時,將 locale 設置爲系統配置的 Native ANSI 字符集,即執行:setlocale(LC_ALL, "")

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

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

由 SBCS 編碼的數據可以隨機訪問,從任意字節偏移開始解編碼都能保證解析出的字符和後繼字符是正確的。

而由MBCS編碼的數據,只能將其作爲字節流進行解析,如果從隨機任意的字節偏移開始解編碼,有可能定位到切斷一個字符的中間位置,導致後繼解析出的字符連續出錯。作爲字節流時,是從某個標識位置進行解析字符,比如從數據的開始位置,或從每個新行符 '\n' 之後開始解析字符。

Unicode 的理解

參考:談談 Unicode 編碼,簡要解釋 UCS、UTF、BMP、BOM 等名詞 by fmddlmyy

首先,Unicode 它不是一個東西,它至少涉及3個方面:Code Point,UCS,UTF。

每個地區、國家都有自己的 Native ANSI 字符集,雖然它們在 ASCII 子集部分是相同的,但其它部分都不盡相同,如果一個字符僅在一個特定的Native ANSI 字符集中編碼,那麼好,如果用戶使用別的字符集,那麼無論如何也無法解析和表現這個字符。怎樣讓你的文本數據同時可以包含英、法、德、中、日、阿拉伯甚至世界上所有可能的、完全的字符?辦法似乎只有一個,就是在世界範圍內對所有可能的字符進行窮舉編碼,這就是最初提出Unicode 的原因,全稱爲 Universal Multiple-Octet Code

  1. Code Point

    在實際編碼之前先給每個窮舉到的字符指定一個序號,叫它 Code Point,把它當做是數學概念,和用幾個字節存儲無關,只要發佈Unicode 的標準化組織(ISO 和 unicode.org)願意,將新出現的字符繼續向後編號就可以了,既然數學序號,就沒有什麼不夠用的問題。編號時有一些原則,就是越常用的字符越靠前,編號到一定數量後,發現差不多了,常用字符都編完了,截止於此將之前的編號組成的子集叫做基本多文種平面(BMP - Basic Multilingual Plane),在 BMP 裏的字符,只要4位16進制數就可以表示,當然在 BMP 以外的字符則需要使用5位或更多16進制數表示。比如:"漢" 字在 BMP 裏,它的 Code Point 可以表示爲 U+6C49。常用CJK漢字都落在 BMP 內,所以都能用U+HHHH 的形式表示其 Code Point。Windows 下有個字符映射表(charmap.exe)的工具,可以列舉每個字符的Code Point、字體支持、字符集之間的關係。

  2. UCS

    有了 Code Point 後就可以規定它的字符集,叫做 UCS - Unicode Character Set,它和存儲有關,用2個字節存儲 Code Point 叫做 UCS-2,用4個字節存儲的叫做 UCS-4,UCS-2 可以編碼並存儲 BMP 中的所有字符,而如果不夠用了(要用到 BMP 外的字符),則可以使用 UCS-4。通常交流中提到 Unicode,如果不特指,就指代的是 UCS-2。

    UCS 和 Native ANSI 字符集採用的 MBCS 編碼是不同的,UCS 不將 ASCII 作爲自己的子集,無論什麼情況 UCS 總使用定長的字節來編碼字符,UCS-2 使用2個字節,UCS-4 使用4個字節,而不是 Native ANSI 字符集中可能採用的變長編碼。比如:"A" 在 GB2312 中編碼爲 0x41,而在 UCS-2 中編碼爲 0x0041。

    當存儲多字節編碼的數據並且不將其作爲字節流解析時,就要考慮保存數據的大小端問題(Endian),可以使用 BOM(Byte Order Mark)標識一個 UCS 字符數據塊是採用 Big Endian 還是 Little Endian 進行存儲:在 Unicode 概念中有一個字符,它的 Code Point 爲 U+FEFF,實際上它不映射到任何地區、國家中的可能字符,即是一個不可能存在字符的 Code Point((-_-^),Unicode 標準對它的註釋爲:ZERO WIDTH NO-BREAK SPACE),當開始處理 UCS 數據塊時,UCS 標準建議先處理這個 ZERO WIDTH NO-BREAK SPACE 字符,比如 UCS-2 數據塊,如果一開始讀到/寫入的字節序列是 FF FE(8 進制:377 376),那麼說明後續的 UCS-2 按 Little Endian 存儲;如果是 FE FF(8進制:376 377),則說明後續的 UCS-2 按 Big Endian 存儲。

    採用定長的 UCS 有一個好處,就是可以像 SBCS 一樣隨機訪問數據塊中的任何字符,當然這裏的隨機偏移單位不是每字節:當用 UCS-2,是每2字節隨機偏移,當用 UCS-4 時,是每4字節隨機偏移。

    但是 UCS 也有缺點,一是有些浪費:比如用 UCS-2,如果在一個數據塊中只使用對應於 ASCII 中的字符,那麼有一半存儲都被浪費掉了,因爲對應於ASCII 中的字符,它的 UCS-2 編碼實際上是它的 ASCII 編碼加上填0的高1字節組成的2字節編碼,那種使用16進制編輯器打開文件後隔一列爲0的字符文件就是這種情況。二是和 ASCII 不兼容,由於太多的已有系統使用 ASCII(或 Native ANSI)了,這點使 UCS 和其它系統對接時有點麻煩。

  3. UTF

    UTF - Unicode Transformation Format,作爲 Unicode 的傳輸編碼,是對 UCS 再次編碼映射得到的字符集,能夠一定程度上解決上面 UCS 的2個缺點。UTF-8 是以8位爲單元對 UCS-2 進行再次編碼映射,是當前網絡傳輸、存儲優選的字符集。UTF-8 使用8位單元(1字節)變長編碼,並將 ASCII 作爲子集,這樣就可以將 UTF-8 當做一種 MBCS 的 Native ANSI 字符集的實現,因此 UTF-8 需要使用1字節流方式解析字符。處於BMP 中的CJK漢字,使用 UTF-8 編碼時通常會映射到3字節序列,而 GB 系列字符集中的CJK漢字通常爲2字節序列。

    UTF-8 和所有的 Native ANSI 字符集一樣:當數據塊中只有 ASCII 子集部分的字符時,是無法區分這個數據塊用哪種 Native ANSI 字符集進行編碼的,因爲這部分的編碼映射關係對於所有的 Native ANSI 字符集是共享的,只有當未來數據塊中包含像CJK漢字這種在 ASCII 子集之外的字符時,採用不同 Native ANSI 字符集的數據塊纔會表現出不同。

    不過有一種方法可以讓數據塊標識自己使用的是 UTF-8 編碼(即使字符內容都在 ASCII 內),這對於文本編輯器等應用很有用,它們可以使用這個標識判斷文件當前使用的字符集,以便未來插入 ASCII 之外的字符時決定如何編碼。這個標識方法就是使用 UCS-2 中 BOM 的 UTF-8 編碼,其1字節流爲:EF BB BF(8 進制:357 273 277)。當數據塊的開始有這個流時就說明後續字符采用 UTF-8 編碼。因爲 UTF-8 使用1字節流方式處理,這時 BOM 已經失去其在 UCS-2 中作爲標識字節序大小端的作用,而僅把 EF BB BF 作爲 UTF-8 編碼的標識功能(Magic),有時就叫它UTF-8 Signature。但並非所有能處理 UTF-8 數據的應用都假定有 Signature 這個標識功能的存在:微軟的應用大多都支持 UTF-8 Signature,但在開源領域,比如 Linux 下有相當多的程序都不支持 UTF-8 Signature。

源文件字符集與編譯

在 ISO C99 中有了寬字符處理的標準,例程大多在 wchar.h 中聲明,並且有了 wchar_t 這麼一個類型。不管哪種 C 編譯器和標準/RT庫實現,wchar_t 通常都可以認爲是存儲 UCS 字符的類型,C 語言語法中也使用前綴的 L 字符來說明一個字符常量、字符串字面量在編譯時採用 UCS 編碼。

VC8 cl的實現中,默認的編譯選項將 wchar_t 做爲內建類型(選項:/Zc:wchar_t),此時 sizeof(wchar_t) 爲 2,可存儲 UCS-2 編碼。Linux GCC 4 的實現中,sizeof(wchar_t) 爲 4,可存儲 UCS-4 編碼。MinGW 和 Cygwin 的 GCC 4 中,sizeof(wchar_t) 爲 2。

如此有這麼一個疑問:

  1. 源文件程序語法中的字符編碼指示。

  2. 源文件自身的字符編碼。

這2者有何種聯繫?於是我做了如下實驗,試着搞明白編譯器對上面2者的處理作用。分別實驗了3個我常用的編譯工具集:VC8、MinGW GCC、Linux GCC。

  • 先看當程序語法中使用非 wchar_t 字符編碼指示時的情況,按照教科書上的說法這種字符串字面量編譯時使用 ASCII 編碼,因爲其中有漢字,因此我把它想象成用某種 Native ANSI 字符集進行編碼,在隨後的測試和調試中便可判斷這種假定是否正確。

    使用記事本、iconv 等工具將上面的源文件做出5份不同的字符集編碼的出來:GBK、UCS-2 LE、UCS-2 LE(BOM)、UTF-8、UTF-8(BOM)。其中 LE 表示UCS-2 採用 Little Endian 字節序存儲;帶 BOM 的表示:在文件頭有 BOM 標識,對於 UTF-8 來說就是 Signature,沒有帶 BOM 的就沒有這個文件頭標識。

    我編譯生成了上面的程序後,查看了3處字符串的編碼:

    1. 內存中的字符串:使用 gdb、VC 等調試工具,跟蹤 memcpy() 時向 buf[] 中複製的字符數據。

    2. 可執行文件中的字串常量:使用 WinHex、hd 等16進制查看工具,在編譯生成的對象文件和可執行映像文件中查找字符串字面量的中間部分 "ABC 123"。

    3. 該程序寫入的文件:該程序使用 fwrite() 向某個命令行參數指定的文件寫入 buf[] 中的字符數據,查看這個寫入文件的編碼。

    實驗結果



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章