GCC有-finput-charset
、-fexec-charset
和-fwide-exec-charset
三个编译选项,用于指定"编译链"中涉及的特定编码。如下所示:
+--------+ -finput-charset +----------+ -fexec-charset (or) +-----+
| source | -------------------> | compiler | -----------------------> | exe |
+--------+ +----------+ -fwide-exec-charset +-----+
参考:GCC编译器选项
我在这里发现了一个关于-finput-charset
的问题:MSVC++中源字符集编码的规范,比如gcc"-finput charset=charset"。但我想知道VC
是否有一个类似GCC中-fexec-charset
的编译器选项来指定执行字符集。
我在Visual Studio中找到了一个看似相对的选项:Project Properties/Configuration Properties/General/Character Set
。值为Use Unicode Character Set
。它的作用与GCC中的-fexec-charset
相同吗?通过这种方式,我想将执行字符集设置为UTF-8。如何?
为什么要设置执行的编码
我正在用C++编写一个应用程序,它需要与数据库服务器通信。表的字符集是utf8。在我构建了一些测试之后,这些测试将捕获数据库表上插入操作引发的异常。异常告诉我它们遇到了不正确的字符串值。我想是编码错误造成的吧?顺便问一句,还有其他方法可以处理这个问题吗?
AFAIK,VC++没有命令行标志来指定UTF-8执行字符集。然而,它确实(偶尔)支持未记录的
#pragma execution_character_set("utf-8")
此处参考。
要使用此pragma获得命令行标志的效果,可以在标头中编写pragma文件,例如preinclude.h
,并通过传递标志CCD_ 11。请参阅本文档了解如何从IDE设置此标志。
pragma在VC++2010中得到支持,然后在VC++2012中被遗忘,现在又得到支持在VC++2013 中
需要注意的是,杂注execution_character_set
似乎只适用于字符串文字("Hello World"
),而不适用于宽字符串文字(L"Hello World"
)。
我做了一些实验来了解源和执行字符集是如何在MSVC中实现的。我在CP_ACP
为1252的Windows系统上使用Visual Studio 2015进行了实验,并将结果总结如下:
字符文字
-
如果MSVC确定源文件是Unicode文件,即以UTF-8或UTF-16编码,则会将字符转换为
CP_ACP
。如果Unicode字符不在CP_ACP
的范围内,MSVC将发出C4566警告("通用字符名'\U0001D575'表示的字符无法在当前代码页(1252)中表示")。MSVC假定编译软件的执行字符集是编译器的CP_ACP
。这意味着您应该在目标环境的CP_ACP
下编译软件,即,如果您想在具有代码页1252的Windows系统上执行软件,您应该在具有任何其他代码页的系统上编译它,而不是执行它。在实践中,如果您的文字是ASCII编码的(C0控件和基本拉丁Unicode块),它可能会起作用,因为大多数常见的SBCS代码页都扩展了这种编码。然而,也有一些没有,特别是DBCS代码页 -
如果MSVC确定源文件不是Unicode文件,它将根据
CP_ACP
解释源文件,并假定执行字符集为CP_ACP
。与Unicode文件一样,您应该在目标环境的CP_ACP
下编译软件,但也会遇到同样的问题。
所有"ANSI"Windows API函数(例如CreateFileA
)根据CP_ACP
或CP_THREAD_ACP
(默认为CP_ACP
)解释LPSTR
类型的字符串。要找出哪些函数使用CP_ACP
或CP_THREAD_ACP
并不容易,因此最好不要更改CP_THREAD_ACP
。
宽字符文字
宽字符文字的执行字符集始终为Unicode,编码为UTF-16LE。所有宽字符Windows API函数(例如CreateFile
)将LPWSTR
类型的字符串解释为UTF-16LE字符串。这也意味着wcslen
不返回Unicode字符数,而是返回宽字符串的wchar_t
字符数。UTF-16在某些情况下也不同于UCS-2。
- 如果MSVC确定源文件是Unicode文件,它会将字符转换为UTF-16LE
- 如果MSVC确定源文件不是Unicode文件,它将根据
CP_ACP
读取文件,并将字符扩展到两个字节,而不解释它们。也就是说,如果一个字符在CP_ACP
中被编码为0xFF
,那么它将被写为0x00 0xFF
,而不管CP_ACP
字符0xFF
是否是Unicode字符U+00FF
我还没有机会在DBCS Windows系统上重复我的实验,因为我不会说通常使用此类代码页的语言。也许有人可以在这样一个系统上重复实验。
对我来说,实验的结论是你应该避免性格文字,即使使用execution_character_set
杂注。
杂注只是更改字符串文字在二进制文件中的编码方式,但不会更改您使用的库或内核的执行字符集。如果您想使用execution_character_set
杂注,您必须重新编译Windows和所有其他与杂注一起使用的库,这当然是不可能的。所以我建议不要使用它。它可能适用于某些系统,因为UTF-8可以与CRT中的大多数字符串函数配合使用,CP_ACP
通常包括ASCII,但你应该检查这些假设是否真的适用于你的目标环境,以及这种滥用所需的努力是否真的值得。此外,pragma似乎没有文档,我可能不会在未来的版本中工作。
否则,您必须为目标系统中正在使用的所有代码页编译单独的二进制文件。避免多个二进制文件的唯一方法是将所有字符串外部化为UTF-16LE编码的资源,并在需要时将字符串转换为CP_ACP
。在这种情况下,您必须将资源脚本(.rc
文件)保存为UTF-8,用/c65001
调用rc
(UTF-16LE不起作用),并包含目标系统中正在使用的所有代码页的字符串。
我建议用Unicode编码(如UTF-8或UTF-16LE)对文件进行编码,如果不能将字符串外部化到资源中并使用定义的UNICODE
和_UNICODE
进行编译,请使用宽字符文字。无论如何,使用字符串和字符文字是不可取的,更喜欢资源。对于需要根据CP_ACP
或某些其他代码页编码的字符串的函数,请使用WideCharacterToMultiByte
和MultiByteToWideChar
。
MSVC的源代码检测启发式算法在启用BOM的情况下效果最好(即使是UTF-8)。
我不是亚洲语言的专家,但我读到Unicode中的汉统一是有争议的。因此,使用Unicode可能不是所有问题的解决方案,也可能存在它不符合要求的情况,但我想说,对于大多数语言来说,Unicode在Windows下最有效。
微软没有明确表示这一点,并记录其编译器和操作系统的行为,这是一个错误。
Visual Studio 2015 Update 2及更高版本支持设置执行字符集:
您可以使用选项/utf-8
,它将选项/source-charset:utf-8
和/execution-charset:utf-8
组合在一起。从上面的链接:
在已经存在无BOM的UTF-8文件或更改为BOM有问题的情况下,请使用/source charset:UTF-8选项正确读取这些文件。
在Linux和Windows之间定位代码时,使用/execution字符集或/utf-8会有所帮助,因为Linux通常使用无BOM的utf-8文件和utf-8执行字符集。
Project Properties/Configuration Properties/General/Character Set
仅设置宏Unicode/MBCS,而不设置源字符集或执行字符集
感谢@user3998276的回答和伟大的实验。
结论告诉我很多
-
当遇到L"字符串"时,宽字符串:
- 编译器首先检测cpp文件编码,然后:
- Unicode-->只使用utf-16//这里可能还有一个转换,比如u8到u16
- ACP-->将Unicode字符串转换为ACP
- 编译器首先检测cpp文件编码,然后:
-
当遇到"字符串"时,普通字符串文字:
- 编译器首先检测cpp文件编码,然后
- Unicode-->将Unicode字符转换为ACP字符
- ACP-->只是根据ACP读取源文件
- 编译器首先检测cpp文件编码,然后
至于您的问题,我认为"数据库表上的插入操作"只是调用数据库插入API。因此,您所需要做的就是用UTF8来组织命令,就像SQL一样。一旦API能够理解您的命令,它就可以为您编写正确的值(想象一下二进制蒸汽)。
尝试:
- 在c++11及更高版本中,可以通过前缀"u8"指定utf-8字符串,如
u8"INSERT INTO table_name (col1, col2,...) VALUES (v1, v2,....)"
http://en.cppreference.com/w/cpp/language/string_literal
-
使用第三方字符串包装器,如QT中的QString。
首先将SQL封装为QString,然后可以轻松地将其转换为utf8、
QByteArray x = mySql.toUtf8()
。这个QByteArray只是"字节数组",因此您可以将其静态化为插入API所需的类型。
再次仔细阅读@user3998276的答案,如果ANSI代码页中有一些字符无法表示,您可能需要将cpp文件的编码更改为Unicode。