MinGW GCC:“未知的转换类型字符’h’”(snprintf)

好吧,我遇到了一个奇怪的问题,在Windows 7上使用MinGW(GCC 4.6.2)编译C文件。该文件包含以下C代码:

#include  int main(int argc, char *argv[]) { printf("%2hhX\n", 250); char c[80]; snprintf(c, sizeof(c), "%2hhX", 250); printf("%s\n", c); return 0; } 

编译结果如下:

 $ gcc.exe -std=c99 -pedantic -Wall test.c test.c: In function 'main': test.c:6:2: warning: unknown conversion type character 'h' in format [-Wformat] test.c:6:2: warning: too many arguments for format [-Wformat-extra-args] 

现在,对我来说很奇怪的是它抱怨第6行的snprintf调用,而不是第4行的printf调用。我错过了什么或警告是不正确的? 另外,格式字符串"%2hhX"可能有更好的等价物吗? (我正在尝试将char变量打印为hex值。)

从历史上看,MinGW在一些奇怪的情况下,特别是在C99支持下。 MinGW主要依赖于随Windows一起发布的msvcrt.dll运行时,并且该运行时不支持C99。

因此,对于较旧版本的MinGW,在使用C99特定格式说明符时,您可能会在C99模式下遇到问题。 同样在历史上,GCC没有为msvcrt.dll缺乏对C99说明符的支持做任何特殊的调整。 因此,您将遇到-Wformat不会警告无法使用的格式的情况。

双方情况都在好转 – 当与MS运行时一起使用时,GCC特别支持-Wformat,例如:

  • -Wpedantic-ms-format以便GCC不会抱怨"I32""I64" (尽管有记录,我仍然抱怨它甚至在4.7.0中都未被识别 – 也许它是全新的)
  • __attribute__((__format__))ms_printf选项

另一方面,MinGW提供了自己的snprintf()一段时间,因为MSVC的变体_snprintf()行为完全不同。 但是,MinGW在msvcrt.dll中的printf()上依赖了很长时间,因此printf() C99格式说明符不起作用。 在某些时候,MinGW开始提供它自己的printf()和朋友版本,这样你就可以获得适当的C99(和GNU?)支持。 但是,似乎在保守的一面,这些并没有最初取代msvcrt.dll版本。 它们的名称类似于__mingw_printf()

看起来在4.6.1和4.7.0之间的某个时刻,MinGW标头开始使用MinGW提供的版本作为msvcrt.dll函数的替换(至少如果你已经指定了C99)。

然而,似乎在较新的版本中,GCC和MinGW仍然有点不同步。 在此之前,GCC不会警告那些实际上不会在MinGW上工作的说明者,不会抱怨那些会产生spcifiers。

您可能需要尝试以下snipet代码来查看您的MinGW版本支持"hhX"

 printf("%hhX\n", 0x11223344); __mingw_printf("%hhX\n", 0x11223344); 

我不确定建议你解决你遇到的问题 – 我认为你可能能够修补MinGW stdio.h头,以便它有一个__attribute__((__format__ (gnu_printf, ...))) printf函数的属性(它们在较新的stdio.h不存在,因此GCC将使用它对格式支持的默认概念)。

除了另一个答案,这里有一些关于GCC中printf格式检查的更多信息:

当你说__attribute__((__format__ (FORMAT, ...)))FORMAT的值可以是(就printf而言)以下之一: printfgnu_printfms_printf

ms_printf使GCC假定该函数采用了用于Microsoft Visual Studio CRT printf族函数的格式字符串。 这意味着GCC会抱怨zhhll ,但会在没有警告的情况下通过I64

gnu_printf使得GCC假设下面是GNU libc printf实现(或者可能只是一个符合POSIX / C99标准的printf实现,我不确定)。 因此,GCC会抱怨I64和其他Microsoft扩展,但会接受zhhll

printf是编译Windows时ms_printf的别名,否则是gnu_printf的别名。

请注意,此检查与正在使用的实际printf实现完全正交。 如果您编写自己的类似printf的函数并将__attribute__((__format__ (FORMAT, ...)))放在上面__attribute__((__format__ (FORMAT, ...)))这很容易看出 – GCC会根据FORMAT抱怨不同的东西,但你可以做任何你想做的事情里面function。

我知道的可用printf实现:

  • MinGW ANSI STDIO(使用-D__USE_MINGW_ANSI_STDIO=1编译)在MinGW.org和MinGW-w64工具链中。 符合ms_printf (完全?)和gnu_printf格式(部分 – 不支持位置参数)。
  • MSVCRT(编译时不带-D__USE_MINGW_ANSI_STDIO=1 )。 符合ms_printf (duh …),与gnu_printf非常低并且取决于运行时版本(旧版本不支持ll ,新版本支持; zhh在任何版本中都不支持到目前为止; GCC幸福地没有意识到虽然这些发展,并假设最坏的情况,VCV 6.0时代的msvcrt,似乎)。
  • gnulib。 完全符合ms_printfgnu_printf (或接近完全)。

MinGW.org中的stdio.h标头不使用attribute format

MinGW-w64中的stdio.h头使用attribute format gnu_printf用于MinGW ANSI STDIO实现,但不使用任何东西用于MSVCRT实现。 修复:在较新版本的MinGW-w64标头中, stdio.h将使用attribute format ms_printf进行MSVCRT实现。

gnulib完全了解printfgnu_printf之间的区别,并且会根据一些复杂的宏来选择其中一个(可能是伴随着一个支持格式所说的正确实现)。

已知(目前)存在GCC格式检查问题的软件:

  • glib – 使用printf格式,但实现来自gnulib; 将它更改为gnu_printf有一个突出的错误
  • CPython – 代码充满了z格式,但官方二进制文件是针对MSVCRT构建的; 它也在其扩展标头中使用printf格式,即使扩展通常也使用z