当前在做一个使用opencv dnn模块来推理yolov8的POC, 使用vc code + cmake 管理C++项目,测试阶段在windows上进行,因此默认选择使用MSVC编译器,后续有可能需要发布到Ubuntu来运行推理程序。
在vs code中通过cmake tool来生成visual studio c++ 解决方案, 然而在进行vc++项目编译时,
控制台中抛出警告“warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失”,如下图所示:
代码页(code page)是Windows操作系统的一类字符的编码方案,编号为936的代码页对应的字符编码为GB2312(参见 Code Page Identifiers - Win32 apps | Microsoft Learn)
也就是说MSVC使用的默认编码是GB2312。
关于Visual Studio中抛出的C4819告警,可以阅读“编译器警告(等级 1)C4819”。
里面还提到了一个解决方法。
通过“File -> Preferences -> Settings”打开设置界面,搜索“files:encoding”查看当前vs code项目中使用文件编码:
通过查看产生告警的cpp文件所在vc++工程的属性,使用的字符集是,“多字节字符集”。
疑问:这里使用的“多字节字符集”指什么? 是“UTF-8”么?
在microsoft的站点找到一篇关于MFC项目的关于“Unicode 和多字节字符集 (MBCS)支持”文档, 里面有提到,如果项目属性选择的是“多字节字符集 (MBCS)”,表示以特定于区域设置的字符集编码的基于 char 的单字节或双字节字符和字符串。 也就是说它由当前操作系统中使用的区域设置来决定使用的字符编码(查看方式:“控制面板->时钟和区域->区域->管理-》更改系统区域设置...”):
由此可以推断当前VC++项目使用的“多字节字符集”是GB2312,和告警中提到的“代码页(936)”,是一致的。
因此,导致这个告警“warning C4819”的根本原因:
当前我使用VS Code的来管理CMake项目和进行代码编写,部分源代码中使用了中文注释,VS Code默认使用UTF-8来保存源代码,而通过CMake生成的VS2022的VC++项目的字符编码却是GB2312,这2种编码对中文字符是无法进行兼容的。
关于“VC++工程属性里的字符集”,可以选择“使用Unicode字符集”或者“使用多字节字符集”,它们之间是互相排斥的。 此选项只控制代码里的API是用宽字符版(即Unicode)的还是ANSI字符版(即GB2312)的,它控制不了源代码中的字符是用Unicode编码还是GB2312编码。
并且这里的“Unicode字符集”指的是UTF-16,而不是“UTF-8”, “UTF-16” 是需要考虑字节序问题(即高位和低位的问题),而“UTF-8”是单字节编码,它在任何类型的CPU中生成的utf-8序列是一样的,因此不用考虑字节序问题。
Unicode 是国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换;
Unicode 字符集的编码范围是 0x0000 - 0x10FFFF , 可以容纳一百多万个字符, 每个字符都有一个独一无二的编码,也即每个字符都有一个二进制数值和它对应,这里的二进制数值也叫 码点, 比如:汉字 "中" 的 码点是 0x4E2D, 大写字母 A 的码点是 0x41,具体字符对应的 Unicode 编码可以查询Unicode字符编码表。
UTF-8、UTF-16、UTF-32 中的 "UTF" 是 "Unicode Transformation Format" 的缩写,意思是"Unicode 转换格式",可以看作Unicode在计算机中的实现。
UTF-8最少需要8个比特位也就是一个字节来存储,是一种变长字符编码,被定义为将码点编码为 1 至 4 个字节,具体取决于码点数值中有效二进制位的数量。
UTF-16 和 UTF-32 分别需要 2 个字节 和 4 个字节来存储。
GB2312 把每个汉字都编码成两个字节,第一个字节是高位字节,第二个字节是低位字节。
我们应该如何解决这个问题呢?
根据前面的分析和资料查阅,我尝试了以下几种方案:
一般这个告警都是因为注释中出现了中文字符,可以将源文件里的中文注释的给删了或者修改成英文注释。这是可行的,
但这种做法有些简单粗暴,仅适合用自己编写的源代码或者在只包含少量中文注释的第三方库。
根据“编译器警告(等级 1)C4819”, 将源文件以Unicode格式保存,操作如下:
在 Visual Studio 中,打开产生告警的源文件,选择“文件-》另存为”。 在“将文件另存为”对话框中,选择“保存”按钮旁的下拉菜单,然后选择“保存时使用编码”。 如果保存到同一文件名,可能需要确认要替换该文件。 在“高级保存选项”对话框中,选择可表示该文件中所有字符的编码(例如,Unicode(带签名的 UTF-8)- Codepage 65001,然后选择“确定”。
不过我目前的源码本身就是以UTF-8格式进行编码的,亲测这种方案并没有解决该MSVC编译器警告问题。
将系统代码页设置为支持源代码使用的字符集(比如VS Code中使用的是UTF-8)。
设置方法:“控制面板->时钟和区域->区域->管理-》更改系统区域设置...”,勾选Beta 版:使用 Unicode UTF-8 获取全球语言支持,然后要重启一下电脑,使更改生效。
注意事项:
尽管目前UTF-8已经成为大部分编程工具使用的默认字符集,这种方案虽然可行,但是有可能产生副作用:
中文Windows系统默认设置为“中文(简体, 中文)”,编码为"GB2312"。上图中如果勾选红框中的选项,则操作系统的默认编码会设为UTF-8。
操作系统的默认编码一般不要修改,如果默认编码设置错了,在非Unicode程序中会出现乱码。
方案三是把操作系统级别字符集修改为UTF-8,这个影响面太大,不好评估副作用,我们是否可以在vc++项目级别来解决这个问题呢?
从输出日志来看,这个告警属于编译阶段产生的,我们是否可以通过给MSVC编译器添加某些控制参数来,设置当前项目的源代码的编码格式呢?
经研究和测试,其实我们可以通过给vc项目的的“C/C++”编译器添加 “/source-charset:utf-8” 或者“/utf-8”来达到。
操作如下:
右键选择有告警的vc项目 -> 打开项目“属性” -> 进入 “C/C++”
更多关于MSVC字符集设置的相关选项说明,可以参考 /utf-8(将源字符集和执行字符集设置为 UTF-8), /execution-charset(设置执行字符集), /source-charset(设置源字符集)。
但问题是,目前vc++项目是在vs code中通过CMake来动态生成的,似乎每次手动修改vc++项目的属性也不是很方便?
经测试,其实我们可以通过在项目最上层的CMakeLists.txt
中添加下面的代码来动态地给MSVC编译器添加“/source-charset:utf-8”选项:
add_compile_options("$<$:/source-charset:utf-8>")
代码解释:通过`$
修改完之后,重新通过CMake构建代码,你会发现“warning C4819”并没有抛出来了,说明问题解决。
重新打开新生成的VS解决方案,检查对应的vc项目的“C/C++”编译器的“命令行”参数,你会发现 “/source-charset:utf-8”自动添加进去了。
备注:
如果在vs code中使用CMake生成vs解决方案时,依然抛出“warning C4819”告警,可是尝试,先删除“build”目录,再重新构建。
安装 Force UTF-8(With No BOM)扩展:
退出VS2022,将会开始安装该插件:
安装完成,从新打开VS 2022解决方案,当你创建新的源代码文件时,相关文件会自动采用UTF-8 No Bom进行保存。
如果你需要在VC++项目中使用当前流行UTF-8 No Bom管理源代码,这个插件倒是可以简化操作。但这个只是影响到文件存储的编码格式,并没有解决该MSVC编译器警告问题。
方案一:源代码我们尽可能使用英文注释,这样更有利于将来进行开源。
方案二和五:只是和文件保存使用的编码格式有关,和MSVC报的该告警没有任何关系。
方案三:影响整个操系统,不好控制副作用。
方案四:最佳方案,可以很好得控制在vc++项目的级别,不同项目之间不会相互影响,并且结合方案五可以很好得实现VS Code和VC Studio之间的源代码共享。
最终建议:
源代码推荐使用使用UTF-8(无BOM) 格式来保存,如果需要通过CMake来生成VC++项目,
可以在CMakeLists.txt中添加下面的语句来保持VC++的项目也使用UTF-8来保存源代码。
编译器中设置编译选项:
# 必须在add_library , add_executable 前设置,否则无效
add_compile_options("$<$:/utf-8>") # 对于C语言
add_compile_options("$<$:/utf-8>") # 对于C++语言
如果需要在VC Studio添加或者修改源代码,可以安装 Force UTF-8(With No BOM) 插件,把源代码修改自动保存为UTF-8(无BOM)。