15

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

#include <stdio.h>

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]

现在,对我来说奇怪的是它抱怨snprintf第 6 行的呼叫,而不是第printf4 行的呼叫。我错过了什么还是警告不正确?此外,格式字符串是否有更好的等价物"%2hhX"?(我正在尝试将 char 变量打印为十六进制值。)

4

2 回答 2

21

从历史上看,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 中也无法识别 - 也许它是全新的)
  • ms_printf选项___attribute__((__format__))

另一方面,MinGW 已经提供了自己snprintf()的一段时间,因为 MSVC 的变体_snprintf(), 行为完全不同。然而,MinGW 长期依赖printf()于 msvcrt.dll,因此 C99 格式说明符printf()不起作用。在某个时候,MinGW 开始提供它自己的版本printf()和朋友,以便您可以获得适当的 C99(和 GNU?)支持。然而,从保守的角度来看,这些最初并没有取代 msvcrt.dll 版本。他们有像__mingw_printf().

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

但是,对于较新的版本,GCC 和 MinGW 似乎仍然有点不同步。和以前一样,GCC 不会警告那些实际上不能在 MinGW 上工作的说明符,而不是抱怨那些会起作用的说明符。

您可能想尝试以下代码片段,以查看您的 MinGW 版本支持的程度"hhX"

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

我不确定建议什么来解决您遇到的问题 - 我认为您可以修补 MinGWstdio.h标头,以便它__attribute__((__format__ (gnu_printf, ...)))在 printf 函数上具有属性(它们不在较新的函数中stdio.h,所以 GCC 将使用它默认的格式支持的想法)。

于 2012-05-21T06:49:54.583 回答
4

除了其他答案之外,这里还有一些关于 GCC 中 printf 格式检查的更多信息:

当您说__attribute__((__format__ (FORMAT, ...)))时, 的值FORMAT可以是(就 printf 而言)以下之一:printf, gnu_printf, ms_printf.

ms_printf使 GCC 假定该函数采用用于 Microsoft Visual Studio CRT printf 系列函数的格式字符串。这意味着 GCC 会抱怨z,hhll, 但会I64毫无警告地通过。

gnu_printf让 GCC 在下面假设 GNU libc printf 实现(或者可能只是一个 POSIX/C99 兼容的 printf 实现,我不确定)。因此 GCC 会抱怨I64和其他微软扩展,但会接受z,hhll.

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

请注意,此检查与正在使用的实际 printf 实现完全正交。如果您编写自己的类似 printf 的函数并__attribute__((__format__ (FORMAT, ...)))使用它,这很容易看出 - GCC 会根据FORMAT.

我知道的可用 printf 实现:

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

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

stdio.hMinGW-w64 中的标头attribute format gnu_printf用于 MinGW ANSI STDIO 实现,但不用于 MSVCRT 实现。已修复:在较新版本的 MinGW-w64 标头中,stdio.hattribute format ms_printf用于 MSVCRT 实现。

printfgnulib 完全了解and之间的区别gnu_printf,并且会根据一些复杂的宏来选择一个或另一个(大概伴随着一个支持格式所说的内容的正确实现)。

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

  • glib - 使用printf格式,但实现来自 gnulib;将其更改为有一个突出的错误gnu_printf
  • CPython - 代码z格式丰富,但官方二进制文件是针对 MSVCRT 构建的;它还printf在其扩展标头中使用格式,即使扩展也经常z使用
于 2014-03-05T21:35:22.277 回答