Visual C ++中Unicode字符的printf函数格式说明符的不幸命运

Windows上的Unicode支持早于大多数其他操作系统出现。 因此,与其他系统中的字符表示相关的问题未能以与其他系统相同的方式解决,在其他系统中,开发人员将新标准的实施推迟到更好的时间[1]。 最有说服力的示例:在Windows上,UCS-2编码用于表示Unicode字符。 Unicode联合会(Unicode Consortium)推荐这样做,因为1.0版仅支持65,536个字符[2]。 五年后,财团改变了主意,但是那时改变Windows中的内容已经太晚了,因为Win32s,Windows NT 3.1,Windows NT 3.5,Windows NT 3.51和Windows 95已经发布到市场上了-它们都使用UCS编码-2 [3]。

但是今天我们将讨论printf函数的格式字符串。

由于Windows早于C在Windows上采用Unicode,这意味着Microsoft开发人员必须弄清楚如何在C运行时中实现对该标准的支持,结果出现了 wcscmpwcschrwprintf之类的功能 。 至于在printf中格式化字符串,为它们引入了以下限定符:

  • %s表示与格式字符串宽度相同的字符串;
  • %S表示宽度与格式字符串的宽度成反比的字符串;
  • %hs代表常规字符串,而与格式字符串的宽度无关;
  • %ws%ls代表宽字符串,而不考虑格式字符串的宽度。

想法是编写这样的代码:

TCHAR buffer[256]; GetSomeString(buffer, 256); _tprintf(TEXT("The string is %s.\n"), buffer); 

并且在ANSI模式下进行编译时,会得到以下结果:

 char buffer[256]; GetSomeStringA(buffer, 256); printf("The string is %s.\n", buffer); 

在Unicode模式下编译时,此[4]:

 wchar_t buffer[256]; GetSomeStringW(buffer, 256); wprintf(L"The string is %s.\n", buffer); 

由于%s的说明符接受与格式字符串宽度相同的字符串,因此此代码将在ANSI和Unicode格式中均正常工作。 同样,此解决方案极大地简化了已编写代码从ANSI格式到Unicode格式的转换,因为所需宽度的字符串替换了%s指定符。

当Unicode支持正式添加到C99时,C语言标准化委员会为printf函数采用了不同的格式字符串模型:

  • %s%hs代表常规字符串;
  • %ls代表一个宽字符串。

那就是问题开始的地方。 在那时的过去六年中,为Windows编写了大量的程序,这些程序的行数达数十亿行,并且它们使用的是旧格式。 如何成为Visual C和C ++编译器?

决定保留旧的非标准模型,以免破坏世界上所有现有的Windows程序。

如果您希望代码在同时遵循printf的经典规则和遵循C标准规则的运行时环境中运行,则必须将常规字符串的%hs限定符和宽字符串的%ls限定为自己。 在这种情况下,无论格式字符串是传递给sprintf还是wsprintf函数 ,都可以保证结果的恒定性。

 #ifdef UNICODE #define TSTRINGWIDTH TEXT("l") #else #define TSTRINGWIDTH TEXT("h") #endif TCHAR buffer[256]; GetSomeString(buffer, 256); _tprintf(TEXT("The string is %") TSTRINGWIDTH TEXT("s\n"), buffer); char buffer[256]; GetSomeStringA(buffer, 256); printf("The string is %hs\n", buffer); wchar_t buffer[256]; GetSomeStringW(buffer, 256); wprintf("The string is %ls\n", buffer); 

单独的TSTRINGWIDTH定义允许编写例如以下代码:

 _tprintf(TEXT("The string is %10") TSTRINGWIDTH TEXT("s\n"), buffer); 

由于人们喜欢以表格形式显示信息,因此这里有一张表格可供您选择。


我用限定符突出显示了用C定义的行,与在Windows [5]中采用的经典格式相同。 如果希望代码在两种格式下产生相同的结果,请使用这些限定符。

注意事项

[1]似乎在Windows上先于其他系统引入Unicode应该使Microsoft拥有了第一步的优势,但是-至少在Unicode方面-这对他们来说变成了“先驱者的诅咒”,因为其他人决定只是等到更好的时候,何时会有更有前途的解决方案(例如UTF-8编码),并且只有在此之后才将Unicode引入他们的系统中。

[2]显然,​​他们认为65,536个字符对于每个人来说已经足够了

[3]后来被UTF-16取代。 幸运的是,对于那些可以用两种编码表示的代码字符,UTF-16与UCS-2向后兼容。

[4]正式而言,Unicode版本应如下所示:

 unsigned short buffer[256]; GetSomeStringW(buffer, 256); wprintf(L"The string is %s.\n", buffer); 

事实是wchar_t尚未成为独立类型,并且在将其添加到标准之前,它只是unsigned short的同义词。 命运wchar_t的曲折可以在另一篇文章中找到。

[5] Windows开发的经典格式首先出现,因此C标准很有可能必须适应它,反之亦然。

译者注

我非常感谢该出版物的作者。 现在变得很清楚如何与“%s”混淆。 事实是,我们的用户不断问这个问题,为什么PVS-Studio对他们看来在他们看来是“便携式”代码的反应不同,这取决于他们是在Linux还是Windows下收集项目。 有必要在专门针对该主题的V576诊断程序的描述中创建一个单独特殊部分(请参见“宽线”)。 在这篇文章之后,一切都变得更加清晰和明显。 我认为应该将此注释读给开发跨平台应用程序的每个人。 阅读并告诉同事。

Source: https://habr.com/ru/post/zh-CN466875/


All Articles