最近在 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编码查询

字符编码与gcc 编译器的编码问题的更多相关文章

  1. gcc编译器对宽字符的识别

    最早是使用VC++工具来学习C++,学的越多就越对VC挡住的我看不见的东西好奇,总想多接触一些开发环境,今日抽空摸索了一下CodeBlocks这个开源的IDE使用方法,配置的编译器是MinGW的gcc ...

  2. C++中宽字符类型(wchar_t)的编码

    转载自: http://www.ituring.com.cn/article/111027 问题的起因是和一个朋友讨论不同编码的转换问题,说到了wchar_t的类型,朋友的看法是,wchar_t的编码 ...

  3. Eclipse设置软tab(用4个空格字符代替)及默认utf-8文件编码(unix)

    简单配置版本: Eclipse設置 一.window->Preferences-> General-Editors->Text Editors , 右边勾选insert spaces ...

  4. 【开发技术】Eclipse设置软tab(用4个空格字符代替)及默认utf-8文件编码(unix)

    Eclipse设置软tab(用4个空格字符代替)及默认utf-8文件编码(unix) 本文摘要: 1.如何配置Eclipse中编辑器支持softtab(用数个空格字符代替默认的tab缩进): 2.如何 ...

  5. 字符编码 + python2和python3的编码区别(day08整理)

    目录 昨日回顾 二十三.元组内置方法 二十四.散列表 二十五.字典内置方法 二十六.集合内置方法 二十七.深浅拷贝 拷贝 浅拷贝 深拷贝 今日内容 二十八.字符编码 1.文本编辑器存储信息的过程 2. ...

  6. gcc编译器与基本类型3

    C语言发展史 1969年贝尔实验室 肯尼斯·蓝·汤普逊,丹尼斯·李奇开发了B语言 ->Unix,New B语言,改名C语言83年提出C语言标准 1989年十二月正式通过C语言标准,C89标准 C ...

  7. 谈谈字符集编码及gb2312、utf-8编码原理

    一.基础中的基础比特位即bit,是计算机最小的存储单位.以0或1来表示比特位的值.Byte是字节数,bit是位数,在计算机中每八位为一字节,也就是1Byte=8bit:Byte和bit都翻译成比特,俗 ...

  8. 你还在为如何区分ASCII编码、GB2312编码、Unicod、UTF-8编码而烦恼吗,一篇文章让你柳暗花明

    字符编码 我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特 ...

  9. gcc编译器的工作流程

    参考资料:http://www.cnblogs.com/dfcao/p/csapp_intr1_1-2.html 在linux系统上,从源文件到目标文件的转化是由编译器完成的.以hello.c程序的编 ...

随机推荐

  1. web图形验证码逻辑

    逻辑:前端生成一个UUID以URL方式发送给后端,后端准备Redis数据库缓存数据,后端拿到UUID后,调用captcha.generate_captcha()生成图片和图片的标签,Redis数据库保 ...

  2. 伽马变换(一些基本的灰度变换函数)基本原理及Python实现

    1. 基本原理 变换形式 $$s=cr^{\gamma}$$ c与$\gamma$均为常数 可通过调整$\gamma$来调整该变换,最常用于伽马校正与对比度增强 2. 测试结果 图源自skimage ...

  3. 死磕JVM之类中各部分的加载顺序

    话不多说,直接上代码: 1.通过new创建对象实例: 2.当对象中含有静态方法,且调用时: -- 调用父类静态方法: 总结: * 类中静态资源首次加载的时间是类中静态资源第一次被调用的时候或者该类的对 ...

  4. Go中的命名规范

    1.命名规范 1.1 Go是一门区分大小写的语言. 命名规则涉及变量.常量.全局函数.结构.接口.方法等的命名. Go语言从语法层面进行了以下限定:任何需要对外暴露的名字必须以大写字母开头,不需要对外 ...

  5. golang 结合实例更好的理解参数传递和指针

    关于参数传递 其实go的参数传递,核心就是一句话:go里所有参数传递都是值传递,既把参数复制一份放到函数里去用. go的函数传参,不管参数是什么类型,都会复制一份,然后新的参数在函数内部被使用. 不像 ...

  6. 关于JSP页面的静态包含和动态包含

    JSP中有两种包含:静态包含:<%@include file="被包含页面"%> 和 动态包含:<jsp:include page="被包含页面&quo ...

  7. echarts legend 限制规定显示个数,显示省略号,修改默认样式

    类似百度统计,有的时候legend的个数比较多,但是前端需要控制初始化显示的个数,以及最多显示的条数,先看效果图: 先给代码: <!DOCTYPE html> <html lang= ...

  8. 测试自动化:java+selenium3 UI自动化(1) - 环境搭建

    1.前言 我大概是在2012年第一次正式接触到自动化测试,那个时候跟随我的团队一起,就当时项目的UI自动化尝试做出了探索. 在我离开那家公司的时候,我们的自动化测试体系仍然难言完美,但是也已经达到了非 ...

  9. Hadoop - YARN Introduce

    YARN Introduce 1. MapReduce1.0缺陷 (1)存在单点故障 (2)JobTracker"大包大揽"导致任务过重(任务多时内存开销大,上限4000节点) ( ...

  10. ABAP 查看采购订单行项目已开票金额和已清金额

    FUNCTION zmm_fm_po_invence. *"----------------------------------------------------------------- ...