关于vc对utf8源码文件的CRLF、LF 换行格式,出现的BOM问题的大坑

时间:2023-01-05 08:56:07

今天碰见个问题,就是朋友从github下载回来的代码,明明函数存在的,但编译时却死活找不到。

例如(不是原型)

// 缓冲区
int a = 1;
int b = a;

error C2065: “a”: 未声明的标识符


很灵异很百思不得其解的错误。后来通过文件重新保存覆盖后问题解决。。

有意思的是,用git/svn等的diff比较工具对比后,却看不到文件有何不同。但检查两文件的md5却不同。

结果看了。两文件都是utf-8 no-bom格式,也就是相同编码,仅仅差别在于,一个是\r\n的CRLF换行的,一个是\n LF换行格式的。


这时,想起以前看过一篇文章,说git的一个AutoCRLF设置的(上传变成LF格式,win下pull回来可以是CRLF的功能)。

但不对的是,朋友明明是github上的zip直接下回来的,应该不至于出这问题吧?

但很忧伤的是,经过研究,github没法解决这种问题。。这是确实存在的问题。


首先,先分析下问题所在。先拿16进制编辑器一看。中文注释那一行

2F 20   E7BC   93E5   86B2   E58C   BA0A    

仔细一数一观察,立刻发现问题所在了。。。众所周知,UTF8的长度是不定长的,有可能1-5字节不一定长度。

而在ANSI/GBK编码下,都是2字节一个汉字。。以上,BA0A这组,换行符0A被前面的当成双字节文本,给吃掉了。

也就是说,编译器没法识别这是个UTF8文件。。按照ASCII/gbk编码去认了

果断测试把文件转成带BOM的UTF8格式,立即正常了。

也就是//中文注释+回车,会把第二行,给吃掉了!


可以测试下,msvc 2010开发环境下,有个开关可以帮助我们检测,那就是【工具 - 选项 - 文本编辑器 - 自动检测不带签名的UTF-8编码】

默认这个开关是开着的,咱们要跟编译器对齐,把他关掉。。

这时,打开不正常的代码文件时,会弹出错误框

【使用 简体中文(GB2312) 编码加载文件 P:\test\cols\cols.cpp 时,有些字节已用 Unicode 替换字符替换。保存该文件将不会保留原始文件内容。】

并且,看到以上错误代码,变成

// 缂撳啿鍖?int a = 1;
int b = a;

看见了吧,第二行果然被注释吃掉了。。

但是这样,看见的中文注释也变成无法识别的乱码了,好忧伤的。

但好处也有,就是std::string aa = "xxxx" 中的xxx可以确保在windows下也是正常的,而不是原生utf8文本,防止意外的执行错误!


可以看出,这问题比较恶心的,也极度危险。在很多时候,你根本不知道藏着这问题。。。

因为如果第二行代码被吃掉了,那么风险太带大了,编译器又没报错(大部分会报warning C4819,但有可能部分字符编码可能就不报这问题),很容易出现灵异BUG,而且是肉眼看不出来的BUG!!!


那么问题来了,UTF8(NotBom) + LF 是常用标准(被某些人称谓国际标准)。比如php,html都不能带bom

之前有文章说的,最好把AutoCRLF关掉,保持UTF8(NotBom)+LF 标准才好。。。但问题,保持了这个标准,vc根本不认啊(2010-2013),直接报错。。于是这扯淡的建议是没用的了。


根据我自身环境来说,因为是协同合作开发,有些人用不同环境是在所难免(而且项目确实有在linux下开发的)

我的建议反而是,要把AutoCRLF开起来,反而能正常使用。。。

也就是代码库上依旧保持LF格式,linux下的用户拉取和上传都是LF+UTF8,(但需要修改,给环境设置成带BOM保存才行,否则也是有风险的)

windows下git同步过来的,都是CRLF代码,也安全了一点点。(不是绝对安全,如果被吃两个字符的话也一样)

当然对于github上,那种直接zip下载的,反而没办法了。


至于补救方法:

  1,git设置上,把autocrlf=true了。

            缺点1:对于不是从git上下载的代码无效(如zip下载)


  2,每次注释时,使用/*空格+中文+空格*/ 格式,这样不会吃换行,吃任何字符都能立刻发现。
          
或者每次注释行后面再加个换行,即两行回车;或者多加两个空格。

            缺点:容易忘了,对于已存在的代码,或他人的代码(公用库)难以控制到。


  这两方法比较hack,扔给后台一个无用的字符去吃掉。。。


  3,开启[将特定警告视为错误] [4819],大部分编码都可以检测到。(可以在视图-属性管理-公用项目配置中设置更方便)。发现error的,全都转为正确的编码文件(如utf8+bom)。

            缺点1:有可能有少量编码无法检测到。

            缺点2:对于一些特殊的代码,要保留文本常量值(运行时)为UTF8的,需要在页面首部加上#pragma execution_character_set("utf-8")


  4,把所有文件都弄成带BOM格式的。同时也要设置开发环境里的保存选项,必须带BOM保存。如果无法解析,则需要升级gcc版本,较为新的gcc、mingw里,可以识别BOM字符。

            缺点1:对于已存在的代码,或他人的代码(公用库)无法检测到。

            缺点2:协同开发时,容易被一不小心修改成无BOM也悄然不知。

            缺点3:大部分环境不支持自动默认保存为UTF8 with Bom格式,如( Eclipse,VS )。需要单独写个程序批量转换。


  5,取消【工具 - 选项 - 文本编辑器 - 自动检测不带签名的UTF-8编码】,及时检测到非bom的源码。出现错误后,关掉(不保存),立即使用工具(notepad++)转换成带bom的文件,再重新打开。

        缺点1:对于已存在的代码,或他人的代码(公用库)无法检测到。

        缺点2:如果开发环境上,同时使用c#,html,css,XML等的编辑开发的话,有可能会无法正常显示。

-----------------

以上,1方法是平时自己从github clone东西时的建议;

2方法是建议平时写代码的建议,防止工程配置被重建,或者源码被人批量格式化的时候,一个补救方法;

3,4是平时的设置,防止不经意间的灵异问题的出现。


=====

建议阅读文章:

http://www.cnblogs.com/zyl910/archive/2012/07/26/cfile_utf8.html