MinGW GCC: "Unknown conversion type character 'h'" (snprintf)

Okay, I've run into a strange issue compiling a C file with MinGW (GCC 4.6.2) on Windows 7. The file in question contains the following C code:

#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;
}

The compilation turns out like this:

$ 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]

Now, what's strange to me is that it complains about the snprintf call on line 6, but not the printf call on line 4. Am I missing something or is the warning just incorrect? Also, is there perhaps a better equivalent for the format string "%2hhX"? (I'm trying to print char variables as hexadecimal values.)


Historically, MinGW has been in a bit of an odd situation, especially as far as C99 support goes. MinGW relies mostly on the msvcrt.dll runtime that's distributed with Windows, and that runtime doesn't support C99.

So with older versions of MinGW, you can run into problems in C99 mode when using C99-specific format specifiers. Also historically, GCC didn't make any special accommodations for msvcrt.dll's lack of support for C99 specifiers. So you'd get into situations where -Wformat wouldn't warn about a format that wouldn't work.

Things are improving on both sides - GCC has specific support for -Wformat when used with the MS runtime, such as:

  • -Wpedantic-ms-format so that GCC won't complain about "I32" and "I64" (even though it's documented, I still get a complaint about it being unrecognized even in 4.7.0 - maybe it's brand new)
  • the ms_printf option to __attribute__((__format__))

On the other side, MinGW has provided its own snprintf() for a while, since MSVC's variant, _snprintf(), behaves quite differently. However, MinGW relied for a long while on the printf() in msvcrt.dll, so C99 format specifiers for printf() didn't work. At some point MinGW started providing it's own version of printf() and friends so that you could get proper C99 (and GNU?) support. However, it seems that to be on the conservative side, these didn't replace the msvcrt.dll versions initially. They have names like __mingw_printf().

It looks like at some point between 4.6.1 and 4.7.0, the MinGW headers started using the MinGW supplied versions as replacements for the msvcrt.dll function (at least if you've specifed C99).

However, it seems that with the newer versions, GCC and MinGW are still a little out of sync. Where as before GCC would not warn about specifiers that wouldn't actually work on MinGW, not it complains about spcifiers that will.

You may want to try the following snipet of code to see how well your version of MinGW support "hhX":

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

I'm not sure what to suggest to fix the problem you're running into - I think that you may be able to patch the MinGW stdio.h header so that it has a __attribute__((__format__ (gnu_printf, ...))) attribute on the printf functions (they're not there in the newer stdio.h, so GCC will use it's default idea of what the format support is).


In addition to the other answer, here is some more info on printf format checks in GCC:

When you say __attribute__((__format__ (FORMAT, ...))), the value of FORMAT can be (as far as printf is concerned) one of the following: printf, gnu_printf, ms_printf.

ms_printf makes GCC assume that function takes a format string intended for Microsoft Visual Studio CRT printf family functions. It means that GCC will complain about z, hh and ll, but will pass I64 without warning.

gnu_printf makes GCC assume GNU libc printf implementation underneath (or maybe just a POSIX/C99-compliant printf implementation, i'm not sure). Therefore GCC will complain about I64 and other Microsoft extensions, but will accept z, hh and ll.

printf is an alias for ms_printf when compiling for Windows, and an alias for gnu_printf otherwise.

Note that this check is completely orthogonal to the actual printf implementation being used. This is easy to see if you write your own printf-like function and put __attribute__((__format__ (FORMAT, ...))) on it - GCC will complain about different things depending on FORMAT, but you can do whatever you want inside the function.

Available printf implementations that i know of:

  • MinGW ANSI STDIO (compile with -D__USE_MINGW_ANSI_STDIO=1) in MinGW.org and MinGW-w64 toolchains. Complies with ms_printf (fully?) and gnu_printf format (partially - does not support positional arguments).
  • MSVCRT (compile without -D__USE_MINGW_ANSI_STDIO=1). Complies with ms_printf (duh...), compliance with gnu_printf is very low and depends on runtime version (old versions did not support ll, new ones do; z and hh are not supported in any version so far; GCC is blissfully unaware of these developments though, and assumes the worst case, msvcrt from VC 6.0 era, it seems).
  • gnulib. Complies with ms_printf and gnu_printf completely (or near-completely).

The stdio.h header in MinGW.org does not use attribute format.

The stdio.h header in MinGW-w64 uses attribute format gnu_printf for MinGW ANSI STDIO implementation, but does not use anything for MSVCRT implementation. FIXED: In newer versions of MinGW-w64 headers stdio.h will use attribute format ms_printf for MSVCRT implementation.

gnulib is fully aware of the difference between printf and gnu_printf, and will pick one or the other depending on some complicated macros (presumably, accompanying it with a proper implementation that supports what the format says it does).

Pieces of software that are known (at the moment) to have problems with GCC format checks:

  • glib - uses printf format, but implementation is from gnulib; there's an outstanding bug for changing it to gnu_printf
  • CPython - the code is full of z formats, but official binaries are built against MSVCRT; it also uses printf format in its extension headers, even though extensions often use z as well