1. 程式人生 > >字元編碼與gcc 編譯器的編碼問題

字元編碼與gcc 編譯器的編碼問題

最近在 vscode 中藉助 gcc 編譯器來配置 c
語言開發環境時,發現中文編碼存在亂碼問題。再加上最近學習到多位元組字元與寬字元,攪在一起,搞得很亂,就把自己的理解寫下來,供有需者參考吧。

1. 字元編碼

先來看維基中關於字元編碼的描述

字元編碼

字元編碼(英語:Character
encoding)、字集碼是把字符集中的字元編碼為指定集合中某一物件(例如:位元模式、自然數序列、8位元組或者電脈衝),以便文字在計算機中儲存和通過通訊網路的傳遞。常見的例子包括將拉丁字母表編碼成摩斯電碼和ASCII。其中,ASCII將字母、數字和其它符號編號,並用7位元的二進位制來表示這個整數。通常會額外使用一個擴充的位元,以便於以1個位元組的方式儲存。
在計算機技術發展的早期,如ASCII(1963年)和EBCDIC(1964年)這樣的字符集逐漸成為標準。但這些字符集的侷限很快就變得明顯,於是人們開發了許多方法來擴充套件它們。對於支援包括東亞CJK字元家族在內的寫作系統的要求能支援更大量的字元,並且需要一種系統而不是臨時的方法實現這些字元的編碼

關於字元編碼的詳細介紹,可以參考 字元編碼筆記

Windows 現在預設所用的漢字編碼仍是 GBK,而 字元編碼筆記中沒有提及,, 因此以下對 GBK 編碼進行相應的介紹。

1.1 GBK 編碼

漢字內碼擴充套件規範

漢字內碼擴充套件規範,稱GBK,全名為《漢字內碼擴充套件規範(GBK)》1.0版,由中華人民共和國全國資訊科技標準化技術委員會1995年12月1日製訂,國家技術監督局標準化司和電子工業部科技與質量監督司1995年12月15日聯合以《技術標函[1995]229號》檔案的形式公佈。

GBK共收錄21886個漢字和圖形符號,其中漢字(包括部首和構件)21003個,圖形符號883個。
GBK的K為“擴充套件”的漢語拼音(kuòzhǎn)第一個聲母。英文全稱Chinese Internal Code Extension
Specification。

字元有一位元組和雙位元組編碼,00–7F範圍內是第一個位元組,和ASCII保持一致,此範圍內嚴格上說有96個文字和32個控制符號。

之後的雙位元組中,前一位元組是雙位元組的第一位。總體上說第一位元組的範圍是81–FE(也就是不含80和FF),第二位元組的一部分領域在40–7E,其他領域在80–FE
也就是說在 GBK 編碼中

  • 對於單位元組的字元,位元組的第一位為 0,後面 7 位為這個符號的 Unicode 碼
  • 對於雙位元組的字元,位元組的第一位為1, 後面的第二位要遵循上面提及的規則

2. 多位元組字元與寬字元

由上面關於編碼的介紹可知,一個字元可能佔據一個位元組,也可能佔據兩個位元組。由於字元在實際儲存時,都是二進位制的格式,因此需要藉助額外的資訊才能判斷出字元的實際位元組數。如,對於GBK編碼來說,其首位為 0,說明其只有 1 個位元組;首位為 1,則說明其有兩個位元組。

為了避免需要額外的資訊,才能判斷出字元中實際的位元組數,則就需要引入寬字元(C語言中的定義為 wchar_t)。c 語言中的寬字元佔據 2 個位元組,其優點是能夠加快字元的解析速度(應為不再需要判斷字元的個數),其缺點也顯而易見,就是會增加記憶體空間的佔用(因為能在多位元組字元中用一個字元表示的字元,用寬位元組也必須要用兩個字元來表示)

多位元組字元和寬字元,有點類似於演算法中運算速度和記憶體佔用的問題,魚與熊掌,不可兼得也。

3. gcc 編譯器配置以及相關例項

之所以會對編碼進行深入的學習,是因為最近在 vscode 上藉助 gcc 來配置 c 語言開發環境時,碰到了漢字亂碼的問題,再加了最近在學習寬字元,所以對編碼知識進行了深入的學習

所用的 gcc 編譯環境如下:

gcc version
8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)

gcc 編譯時,預設會按照 c 檔案的編碼進行編碼,所以 c 檔案的編碼要和 cmd 命令視窗的編碼格式相同,不然就會出亂碼。
windows cmd 的預設的編碼為 gbk,如下圖:

因此,c 檔案的編碼也要為 gbk,才能保證漢字不亂碼。當然也可以通過 chcp 65001 來將cmd 的編碼格式改為utf-8,這樣 utf-8 編碼的c 檔案輸出的漢字就不會亂碼。

如果要把 cmd 改為預設的編碼,則使用 chcp936命令即可。

當然,如果不想修改預設的 cmd 編碼,又想避免由於 c 檔案的編碼與 cmd 預設編碼不匹配所導致的亂碼問題,可以在 gcc 的編譯選項中加入 -fexec-charset=gbk 來避免亂碼。

3.1 多位元組字元例項

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

void main()
{
    char str[10] = "李";
    int a, b, c;
    printf("%#X %#X %#X\n", (unsigned char)str[0], (unsigned char)str[1], (unsigned char)str[2]);
    printf("length: %d\n", strlen(str));
    a = printf("%c%c%c", str[0], str[1], str[2]);  // 輸出 3 個位元組
    putchar('\n');
    b = printf("%c%c", str[0], str[1]);  // 輸出 2 個位元組
    putchar('\n');
    printf("a = %d, b = %d", a, b);
}
  • 編譯時採用 utf-8 編碼

    gcc -fexec-charset=gbk utf8.c -o utf8.exe
  • cmd 視窗採用 utf-8 編碼的輸出

    • 從圖中的結果可看出,連續輸出 3 個位元組可以輸出正確的漢字,這是由於 utf-8 中,常用漢字為 3 個位元組。

    • 只輸出兩個位元組時,沒法顯示內容。從 b = 2 也可以看出,輸出成功,說明不能顯示的原因是 cmd 解析出錯。出錯的原因是因為其第一個位元組已經指定了當前字元包含 3 個位元組,而實際只輸出兩個位元組,導致 cmd 視窗解析失敗。

      utf-8 的編碼規則

      UTF-8 的編碼規則很簡單,只有二條:

      1)對於單位元組的符號,位元組的第一位設為0,後面7位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。

      2)對於n位元組的符號(n > 1),第一個位元組的前n位都設為1,第n + 1位設為0,後面位元組的前兩位一律設為10。剩下的沒有提及的二進位制位,全部為這個符號的 Unicode 碼。

      下表總結了編碼規則,字母x表示可用編碼的位。

      Unicode符號範圍     |        UTF-8編碼方式
      (十六進位制)        |              (二進位制)
      ----------------------+---------------------------------------------
      0000 0000-0000 007F | 0xxxxxxx
      0000 0080-0000 07FF | 110xxxxx 10xxxxxx
      0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
      0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  • cmd 視窗採用 gbk 編碼輸出

    • 鏉(shòu 鋒利的意思) 對應的 GBK 編碼是 E6 9D,因此列印 3 個位元組和 2 個位元組時,由於 cmd 的顯示的編碼為 GBK,所以會輸出 鏉,跟實際檔案中的”李“相比,就是亂碼
    • 輸出 3 個位元組時,E6 9D 會被解析為 鏉。8E 會解析失敗,因為其首位大於 1,按照 GBK 的編碼規則,其應該有兩個位元組,而其只有一個位元組,因此解析失敗,沒有輸出

3.2 寬字元例項

// 檔名 wide_char.c,編碼 utf-8
#include<stdio.h>
#include<locale.h>

void main()
{
    setlocale(LC_CTYPE, "");  // 設定本地化,不然寬字元無法正常顯示 
    wchar_t wch = L'李'; // 寬字元的定義
    wprintf(L"%c\n", wch);
    printf("%lc\n", wch);

    // 輸出結果為 2,2 說明一個寬字元佔據兩個字元
    printf("%d %d\n", sizeof(wch), sizeof(wchar_t));  

    // 編譯編碼為 utf-8 則輸出為 6,5
    // 編譯編碼為 gbk, 則輸出結果為 6, 4
    // 以上結果說明一個寬字元固定佔據兩個位元組
    printf("%d %d", sizeof(L"1李"), sizeof("1李"));
}
  • 編譯執行過程

    • 從結果中可看出,在執行的過程中,沒有改變 cmd 的編碼格式,但是漢字輸出沒有亂碼。

    • c 語言的寬字元藉助 Unicode 來實現,因此在使用寬字元時, c 檔案的編碼格式最好是 utf-8。如果編碼格式為 gbk,則編譯時會報錯

相關網站

gbk 編碼查詢

utf-8編碼查