<inttypes.h> vs <stdint.h> mismatch

Asked by Kevin Bracey

When compiling C99 for Cortex-M (and presumably for any 32-bit ARM), some types end up misaligned between <stdint.h> and <inttypes.h>.

For example, <stdint.h> typedefs int_fast32_t as "int", but <inttypes.h> defines PRIdFAST32 as "ld" instead of "d".

Which means you get format warnings from:

  int_fast32_t x = 3;
  printf("x=%" PRIdFAST32 "\n", x);

We did have some macro magic to try detect the problem toolchain and redefine those macros correctly, but it was too fragile, and no longer works on the current version. Any chance of a proper fix?

The basic cause seems to be that <stdint.h> is taking direction on how to define the types from built-in gcc macros __INT_FAST32_TYPE__ etc, but <inttypes.h> doesn't use those macros, and tries to guess the types by inspecting INT_FAST32_MAX, guessing "long" wrongly.

Question information

English Edit question
GNU Arm Embedded Toolchain Edit question
No assignee Edit question
Last query:
Last reply:
Revision history for this message
Kevin Bracey (kevin-bracey) said :

Digging further, seems like a newlib problem. And it's changed slightly since I first encountered the bug.

PRId32 is now defined by checking what __INT32_TYPE__ is, so that's solid.

Then PRIdFAST32 is set to the same thing as PRId32. Unfortunately "int32_t" is "long" and "int_fast32_t" is "int", so that fails.

Now, most of the mismatches just lead to warnings, but I can see two real code-breaking cases:

   int_fast8_t is "int". If defined, SCNdFAST8 will be "hhd"
   int_fast16_t is "int". If defined, SCNdFAST16 will "hd"

It seems that inttypes has a broken assumption all the way down that all "least" and "fast" types are exactly the specified size.

Revision history for this message
Andre Vieira (andre-simoesdiasvieira) said :

Hi Kevin,

I have submitted this issue to the newlib mailing list and we are working towards a solution.

See https://sourceware.org/ml/newlib/2015/msg00540.html for more details.

Kind Regards,

Revision history for this message
Kevin Bracey (kevin-bracey) said :

Thanks! You should probably point out the SCNdFAST8 problem too, as that's a far more obvious and less subtle version of the same error than the PRIdFAST32 one. This isn't actually just something that arises when int is the same size as long; it's more serious.

The incorrect assumption is that "int_fastX_t" == "int_leastX_t" == "intX_t" for every size.

eg Newlib assumes int_fast8_t is the same as int8_t, but it's actually 32 bits on this system.

Revision history for this message
Tejas Belagod (belagod-tejas) said :

An answer has been proposed for this, hence changing status.

Revision history for this message
Andre Vieira (andre-simoesdiasvieira) said :

Hi Kevin,

The patch was approved upstream and applied. See https://sourceware.org/ml/newlib/2015/msg00635.html.

This should be part of our next toolchain release.

Thank you for reporting and I hopes this solves your issue!

Revision history for this message
Kevin Bracey (kevin-bracey) said :

Thanks for your work! Looks like it should do the job.

As an aside, does the ARM EABI actually define the sizes of int_fast types these days?

I've seen disagreements over whether int_fast8_t should be 8 or 32 bit in the past, and for binary compatibility you would need to pin that down. But I don't recall ever seeing it mentioned in an EABI doc.

Revision history for this message
Erlkoenig (profclonk) said :

It looks like this update breaks PRIu8 (and probably other 8/16 conversion macros), which now resolves to "hhu". Because newlib is being built without "--enable-newlib-io-c99-formats" ./configure option, the macro _WANT_IO_C99_FORMATS isn't defined, so that newlib doesn't recognize the "hh" size parameter and just prints "hu" instead of the number.

The old behaviour was to define PRIu8 as "u" which had newlib treat the uint8_t Argument as "unsigned int" which was correct because char arguments get promoted to int anyways.
The solution would probably be to define PRIu8 as "u" again, or enable the --enable-newlib-io-c99-formats command line option, which might result in increased library size.

My testcase:
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>

void test () {
  uint8_t x = 42;
  printf ("%" PRIu8, x);

It just prints "hu". The string literal ends up in the resulting binary file as "%hhu".

Revision history for this message
Kevin Bracey (kevin-bracey) said :

Trying to use C99 inttypes.h without having c99 formats enabled in stdio seems like a clear library configuration error. Anyone using inttypes.h should have enabled C99 in stdio.

You can sort of sidestep it for your example by defining PRIu8 as "u" rather than "hhu", but that won't work for SCNu8. And in general, which formats will and won't work without C99 stdio will be somewhat platform-specific, so it's playing with fire.

Not clear to me from the spec whether it matters whether PRIu8 is "hhu" or "u".

"hhu" narrows its argument, so printf("%hhu", 0x101) is apparently defined as printing "1".

Whether printf("%"PRIu8, 0x101) is required to do that or not, isn't clear. My guess is that it should - the intent is presumably that it's supposed to be analogous to the normal printf formats.

Revision history for this message
Andre Vieira (andre-simoesdiasvieira) said :


Just to clarify something on my end, you are passing the _WANT_IO_C99_FORMATS macro when including inttypes though you are using a newlib that you built without _WANT_IO_C99_FORMATS?


Revision history for this message
Erlkoenig (profclonk) said :

I think your guesses are correct. Since C99 formats are disabled in the gcc-arm-embedded binary distributions, PRIu8 should be defined as "u" and SCNu8 not be defined at all in the library header, such that it works at least partially, just as it did with the old version. Alternatively don't define PRIu8 at all to make it easier for the user to find out that C99 formats are not (fully) supported.

No, i did not define _WANT_IO_C99_FORMATS, and the newlib included in the binary downloads doesn't define it either. My example code compiled with the current binary distribution will just print "hu", while it prints "42" with the old versions.

Revision history for this message
Spencer Oliver (spenoliver) said :

I am seeing the same as Erlkoenig using 4.9 update 3. PRIu8 is being defined as "hhu" rather than "u".
4.9 update 2 works as expected.

Any suggestions?

Revision history for this message
Andre Vieira (andre-simoesdiasvieira) said :

Hi Spencer,

As Kevin points out trying to use C99 inttypes.h without having a C99 stdio is not a good idea. I suspect you might be compiling for newlib-nano, which is configured without C99 to save space, so you can't use PRIu8 here. If you were to pass "-std=c90 -Wall -pedantic', the compiler will warn you that ISO C90 does not support the 'hh' length modifier.

A work around would be to redefine PRIu8 as 'u'.

The previous version of the toolchain defined this in inttypes but incorrectly as for C99, PRIu8 should really be representing the 8-bit unsigned integer variant of the given target, which in this case is 'hhu'.

Hope this helps.

Best Regards,

Revision history for this message
Spencer Oliver (spenoliver) said :


Would it not make sense to issue a warning/error if the user if using these types without _WANT_IO_C99_FORMATS being defined?

Can you help with this problem?

Provide an answer of your own, or ask Kevin Bracey for more information if necessary.

To post a message you must log in.