字符編碼與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,如下圖:

cmd 的默認編碼格式

因此,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 編碼的輸出

    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 編碼輸出

    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李"));
}
  • 編譯運行過程

    wide_char 編譯運行過程

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

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

      gbk 編碼格式的源文件,編譯會報錯

相關網站

gbk 編碼查詢

utf-8編碼查詢


免責聲明!

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



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