• 谁删了服务器?谈VC源码字符集和回车换行注意事项


    在Windows Visual C/C++编程时,经常需要从Github或者其他既有项目中“借鉴”(呵呵哒)一些代码过来。这个时候,就要格外注意字符集和回车换行带来的编译问题。

    这类编译问题造成的错误千奇百怪,可能在编译时、链接时,甚至运行时造成困扰。尤其是运行时,很难发现,在极其倒霉的时候,会造成删库跑路或者惨痛的事故。

    1. 源代码文件的规格

    源代码文件存储为可见文本,但各个平台、工具链上具体的编码规格是略有区别的。对含有中文等非ASCII标准字符的源代码,会遇到编码/字符集的概念。对跨越Linux、Windows环境的源代码,会涉及换行符的概念。

    • 编码/字符集: windows下,Visual Studio创建的C语言源文件一般存储为 本地字符集(如中文windows就是GB2312)。Linux下,默认是UTF-8的。
    • 换行符: windows下,Visual Studio创建的C语言源文件每行代码后面是两个不可见字符,即\015\012,也就是\n\r,而Linux默认只有\n.

    下表是几类C++ IDE/编辑器常用(默认配置或常用配置)例子。

    操作系统编辑器/IDE编码/字符集回车/换行
    WindowsVisual StudioANSI(GB2312系)\n\r
    WindowsVisual Studio CodeUTF-8\n\r
    WindowsQt CreatorUTF-8\n\r
    WindowsCodeBlocksANSI(GB2312系)\n\r
    WindowsNotepadANSI(GB2312系)\n\r
    Linux大部分编辑器/IDEUTF-8\n

    因此,如果引用的代码原本就是Linux下的,那它十有八九是UTF-8+\n的配置组合。如果引用的是既往合作企业的Visual C++头文件,那通常都是GB2312+\n\r的组合。

    2. Visual C++编译器踩坑

    Visual C++编译器默认处理的是 ANSI本地字符集,若用的是中文Windows,则默认.c、.h文件里都是GB2312编码的汉字+\n\r换行。如果直接引用了非默认格式的代码,则会有很多现象。

    汉字编码回车换行编程工序可能故障规避方法
    GB2312\n\r默认
    GB2312\n一般没问题
    UTF-8\n\r编译嵌入式注释导致编译报错

    1.首末汉字前后插入多个空格

    2.独立注释,后续间隔空行

    UTF-8\n编译嵌入式注释导致编译报错

    1.首末汉字前后插入多个空格

    2.独立注释,后续间隔多个空行

    UTF-8\n链接导出符号名称不正确或含有奇怪字符转换源码为\n\r存储
    UTF-8运行乱码

    转换源码为GB2312存储

    Qt用QString::from编码临时解决

    Qt用英文界面+翻译解决

    UTF-8运行崩溃数字节和数字符得到的数字不一致,也不是x2关系。UTF-8存在3字节汉字。转换源码为GB2312存储
    UTF-8运行意外的结果多字节汉字导致if等语句被吃掉了。转换源码为GB2312存储

    3. 原因分析

    UTF-8编码通过首字节连续1的个数来确定当前字符使用几个字节来表示。其原理引用网络截图如下表
    在这里插入图片描述编译器用GB2312的定长2字符来处理非ASCII可见字符,而3字节以上的汉字会打破这种奇偶属性。因此,如果错误的用GB2312来解释UTF-8,对3字节以上的编码,后续的字节会“粘”到下一个正常ASCII字符的起点上去:
    比如下面这个字符:

    1110xxxx 10xxxxxx 10xxxxxx 00010111
    
    • 1

    本应是1个汉字+1个字母,却被解释为2个汉字。

    为什么插入空格有用: 错误的UTF-8译码带来的误差,在尾部存在多个空白字符时可能被抵消,因此拥有很多尾部空格的汉字一般不会造成编译错误。

    为什么\n换行比\n\r换行错误更古怪: 因为\n\r相当于2个字符,可以比\n一个字符纠正更多的解析误差。

    3.1 隐晦的链接错误

    隐晦的错误是多行函数定义里的注释:

    /**!...巴拉巴拉...导出DLL
    一堆Doxygen注释
    */
    DLL_EXP_API  
    /*返回值修改巴拉巴拉 2021-02*/
    int 
    STDCALL fun (
    	int p1, /*bala*/
    	int p2, /*bala*/
    )
    {
    	//...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    曾经出现过,因为相邻两个注释块因为字节数误差连成一体,变成了:

    /**!...巴拉巴拉...
    #$%^&**%乱七八糟L_-02*/
    int 
    STDCALL fun (
    	int p1, /*bala*/
    	int p2, /*bala*/
    )
    {
    	//...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    结果死活报链接错误!因为这个符号压根没有导出。

    3.2 危险的运行时错误

    被吃掉的if:

    //...巴拉巴拉...
    //一顿注释猛如虎
    if(safe(root))/*这安全了吧*/
    	if(auto_del && partion_size(root_id) >= MAX_C)
    		deleteAll(root);/*一堆IF绝对安全*/
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    变成了:

    //...巴拉巴拉...
    //一顿%$全了吧*/
    	if(auto_del && partion_size(root_id) >= MAX_C)
    		deleteAll(root);/*芭比Q了,时怎么断到这里的?!*/
    
    • 1
    • 2
    • 3
    • 4

    这种错误在测试时也是比较难发现的,尤其是当MAX_C相对较大的时候。
    Babiq

    4. 建议操作-使用Git批量维护差异

    既然使用Visual Studio 作为开发工具,建议还是尽可能使用默认的编码,除非Visual Studio 向 MinGW 等第三方编译器,或者Linux平台输出库,此时建议使用UTF-8编码。复用代码要注意这些事情,有没有什么比较简单和普适的批处理方法呢?

    有的,可以使用Git服务作为中转。Windows的Git可以设置编码自动转换:

    user@Dev283$ git config --global core.autocrlf true
    
    • 1

    设置后,在克隆和签出等操作中,会把代码转换为windows换行符;在哈希、提交时,会转换回Linux换行符。

  • 相关阅读:
    设计一个程序统计某班全体学生3门课的考试成绩。要求先输入学生人数,并输入每个学生的三门成绩,统计出每门课程的全班平均分及每个考生所有考试的总分。(二维数组)
    Spring Boot + EasyUI 创建第一个项目(一)
    【vue后台管理系统】基于Vue+Element-UI+ECharts开发通用管理后台(上)
    企业媒体信息发布系统,一键发布省时省力
    Kamiya丨Kamiya艾美捷抗冷休克结构域蛋白E1说明书
    泛在电力物联网的关键技术与未来发展策略-安科瑞黄安南
    数据包守恒 & TCP 拥塞控制
    23、STM32——CAN
    讯飞大数据竞赛2022 汽车领域多语种迁移学习 打卡博客
    【钰娘娘】1373. 二叉搜索子树的最大键值和 DFS
  • 原文地址:https://blog.csdn.net/goldenhawking/article/details/126502721