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而言)以下之一: printf
, gnu_printf
, ms_printf
。
ms_printf
使GCC假定该函数采用了用于Microsoft Visual Studio CRT printf族函数的格式字符串。 这意味着GCC会抱怨z
, hh
和ll
,但会在没有警告的情况下通过I64
。
gnu_printf
使得GCC假设下面是GNU libc printf实现(或者可能只是一个符合POSIX / C99标准的printf实现,我不确定)。 因此,GCC会抱怨I64
和其他Microsoft扩展,但会接受z
, hh
和ll
。
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
,新版本支持;z
和hh
在任何版本中都不支持到目前为止; GCC幸福地没有意识到虽然这些发展,并假设最坏的情况,VCV 6.0时代的msvcrt,似乎)。 - gnulib。 完全符合
ms_printf
和gnu_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完全了解printf
和gnu_printf
之间的区别,并且会根据一些复杂的宏来选择其中一个(可能是伴随着一个支持格式所说的正确实现)。
已知(目前)存在GCC格式检查问题的软件:
- glib – 使用
printf
格式,但实现来自gnulib; 将它更改为gnu_printf
有一个突出的错误 - CPython – 代码充满了
z
格式,但官方二进制文件是针对MSVCRT构建的; 它也在其扩展标头中使用printf
格式,即使扩展通常也使用z