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

2

15

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.)

Nicotiana answered 20/5, 2012 at 23:39 Comment(7)
Interestingly, it works fine with GCC 4.3.4: ideone.com/LAPP9. I've also tried with 4.1.2, and it's fine on that too.Headforemost
Using MinGW GCC 4.6.1 I get warnings on both the printf() and the snprintf() - what MinGW distro are you using? I'm currently using the TDM distribution.Rafaellle
@MichaelBurr: Uh, I didn't even realize that there were several MinGW distros. I'm using the "standard" one, I guess (mingw.org, installed with sourceforge.net/projects/mingw/files/Installer/mingw-get-inst). Would that make a difference, though?Nicotiana
Also, I'm even more confused now that we have 3 different behaviors on 4 versions of GCC. The format string is valid, though, isn't it?Nicotiana
@Socob: I generally use TDM's MinGW (tdm-gcc.tdragon.net) and/or nuwen's (nuwen.net/mingw.html). I've found them to be much easier to install than the official MinGW (nuwen's is literally unpacking an archive), they package additional useful libraries (for example, pthreads-win32 in TDM and Boost in nuwen) and TDM has a option for a 64-bit targeting compiler.Rafaellle
@Socob: all I know for sure is that 4.6.1's snprintf() doesn't seem to handle "%2hhX" as I expect - it ignores the additional h so if you pass in a negative number or an unsigned with a value larger than 255 you'll get more than two characters printed out. I think that technically that's undefined behavior, but still if you're expecting only the least significant byte to be printed, some further test might be in order. 4.7.0 works as expected, I don't have a 4.6.2 to test.Rafaellle
@Michael: For me it works just as expected (4.6.2); regardless of whether I pass in negative or large values, I only get two characters/the least significant byte.Nicotiana
R
21

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).

Rafaellle answered 21/5, 2012 at 6:49 Comment(2)
Your code snippet gives no warnings when I compile it with the options from above and prints 44 twice, as one would expect; it seems that only snprintf in particular is affected by the warning on my installation. Yet, the code I posted above prints FA twice, so the function seems to be working correctly. So, essentially, the warning is indeed incorrect and it's just a quirk with MinGW and GCC? If so, that's all I really need to know - I just wanted to figure out if I missed something in the code or if I could ignore the warning.Nicotiana
Thanks. Even on MinGW 4.9.1 __mingw_printf is the one that works for me without warnings. Will wrap it in a macro for generality.Propagandism
T
4

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
Takao answered 5/3, 2014 at 21:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.