最近在 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); }