15
votes

Say I want to print unsigned char:

unsigned char x = 12;

which is correct. This:

printf("%d",x);

or this:

printf("%u",x);

?

The thing is elsewhere on SO I encountered such discussion:

-Even with ch changed to unsigned char, the behavior of the code is not defined by the C standard. This is because the unsigned char is promoted to an int (in normal C implementations), so an int is passed to printf for the specifier %u. However, %u expects an unsigned int, so the types do not match, and the C standard does not define the behavior

-Your comment is incorrect. The C11 standard states that the conversion specifier must be of the same type as the function argument itself, not the promoted type. This point is also specifically addressed in the description of the hh length modifier: "the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing"

So which is correct? Any reliable source saying on this matter? (In that sense we should also print unsigned short int with %d because it can be promoted to int?).

4

4 Answers

10
votes

The correct one is*:

printf("%d",x);

This is because of default argument promotions as printf() is variadic function. This means that unsigned char value is always promoted to int.

From N1570 (C11 draft) 6.5.2.2/6 Function calls (emphasis mine going forward):

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

and 6.5.2.2/7 subclause tells:

The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

These integer promotions are defined in 6.3.1.1/2 Boolean, characters, and integers:

If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.58) All other types are unchanged by the integer promotions.

This quote answers your second question of unsigned short (see comment below).


* with exception to more than 8 bits unsigned char (e.g. it might occupy 16 bit), see @chux's answer.

9
votes

Correct format specifier for unsigned char x = 12 depends on a number of things:

If INT_MAX >= UCHAR_MAX, which is often the case, use "%d". In this case an unsigned char is promoted to int.

printf("%d",x);

Otherwise use "%u" (or "%x", "%o"). In this case an unsigned char is promoted to unsigned.

printf("%u",x);

Up-to-date compilers support the "hh" length modifier, which compensates for this ambiguity. Shouldx get promoted to int or unsigned due to the standard promotions of variadic parameters, printf() converts it to unsigned char before printing.

printf("%hhu",x);

If dealing with an old compiler without "hh" or seeking highly portable code, use explicit casting

printf("%u", (unsigned) x);

The same issue/answer applies to unsigned short, expect INT_MAX >= USHRT_MAX and use "h" instead of "hh".

2
votes

Both, unsigned char and unsigned short, can always safely be printed with %u. Default argument promotions convert them either to int or to unsigned int. If they are promoted to the latter, everything is fine (the format specifier and the type passed match), otherwise C11 (n1570) 6.5.2.2 p6, first bullet, applies:

  • one promoted type is a signed integer type, the other promoted type is the corresponding unsigned integer type, and the value is representable in both types;

The standard is quite clear that default argument promotions apply to the variadic arguments of printf, e.g. it's mentioned again for the (mostly useless) h and hh length modifiers (ibid. 7.21.6.1 p7, emph. mine):

hh -- Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing); [...]

1
votes

For cross platform development, I typically bypass the promoting issue by using inttypes.h

http://pubs.opengroup.org/onlinepubs/009695399/basedefs/inttypes.h.html

This header (which is in the C99 standard) defines all the printf types for the basic types. So if you want an uint8_t (a syntax which I highly suggest using instead of unsigned char) I would use

#include <inttypes.h>
#include <stdint.h>
uint8_t x;
printf("%" PRIu8 "\n",x);