VC 在 GCC 中是否有像"-fexec-charset"这样的编译选项来设置执行字符集?



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_ACPCP_THREAD_ACP(默认为CP_ACP)解释LPSTR类型的字符串。要找出哪些函数使用CP_ACPCP_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或某些其他代码页编码的字符串的函数,请使用WideCharacterToMultiByteMultiByteToWideChar

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文件编码,然后
      • Unicode-->将Unicode字符转换为ACP字符
      • ACP-->只是根据ACP读取源文件

至于您的问题,我认为"数据库表上的插入操作"只是调用数据库插入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。

最新更新