Yes, printf
with "%c"
requires an int
argument -- more or less.
If the argument is of a type narrower than int
, then it will be promoted. In most cases, the promotion is to int
, with well defined behavior. In the very rare case that plain char
is unsigned and sizeof (int) == 1
(which implies CHAR_BIT >= 16
), a char
argument is promoted to unsigned int
, which can cause undefined behavior.
A character constant is already of type int
, so printf("%c", 'x')
is well defined even on exotic systems. (Off-topic: In C++, character constants are of type char
.)
This:
unsigned int foo = 42;
fprintf(fp, "%c\n", foo);
strictly speaking has undefined behavior. N1570 7.1.4p1 says:
If an argument to a function has ... a type (after promotion) not
expected by a function with variable number of arguments, the behavior
is undefined.
and the fprintf
call clearly runs afoul of that. (Thanks to ouah for pointing that out.)
On the other hand, 6.2.5p6 says:
For each of the signed integer types, there is a corresponding (but
different) unsigned integer type (designated with the keyword
unsigned) that uses the same amount of storage (including sign information) and has the same alignment requirements.
and 6.2.5p9 says:
The range of nonnegative values of a signed integer type is a subrange
of the corresponding unsigned integer type, and the representation of
the same value in each type is the same.
with a footnote:
The same representation and alignment requirements are meant to imply
interchangeability as arguments to functions, return values from
functions, and members of unions.
The footnote says that function arguments of types int
and unsigned int
are interchangeable, as long as the value is within the representable range of both types. (For a typical 32-bit system, that means the value has to be in the range 0 to 231-1; int
values from -231 to -1, and unsigned int
values from 231 to 232-1, are outside the range of the other type, and are not interchangeable.)
But footnotes in the C standard are non-normative. They are generally intended to clarify requirements stated in the normative text, not to impose new requirements. But the normative text here merely states that corresponding signed and unsigned types have the same representation, which doesn't necessarily imply that they're passed the same way as function arguments. In principle, a compiler could ignore that footnote and, for example, pass int
and unsigned int
arguments in different registers, making fprintf(fp, "%c\n", foo);
undefined.
But in practice, there's no reason for an implementation to play that kind of game, and you can rely on fprintf(fp, "%c\n", foo);
to work as expected. I've never seen or heard of an implementation where it wouldn't work.
Personally, I prefer not to rely on that. If I were writing that code, I'd add an explicit conversion, via a cast, just so these questions don't arise in the first place:
unsigned int foo = 42;
fprintf(fp, "%c\n", (int)foo);
Or I'd make foo
an int
in the first place.