9
votes

The printf family of functions provide a series of length modifiers, two of them being hh (denoting a signed char or unsigned char argument promoted to int) and h (denoting a signed short or unsigned short argument promoted to int). Historically, these length modifiers have only been introduced to create symmetry with the length modifiers of scanf and are rarely used for printf.

Here is an excerpt of ISO 9899:2011 §7.21.6.1 “The fprintf function” ¶7:

7 The length modifiers and their meanings are:

  • 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); or that a following n conversion specifier applies to a pointer to a signed char argument.

  • h Specifies that a following d, i, o, u, x, or X conversion specifier applies to a short int or unsigned short intargument (the argument will have been promoted according to the integer promotions, but its value shall be converted to short int or unsigned short int before printing); or that a following n conversion specifier applies to a pointer to a short int argument.

  • ...

Ignoring the case of the n conversion specifier, what do these almost identical paragraphs say about the behaviour of h and hh?

  • In this answer, it is claimed that passing an argument that is outside the range of a signed char, signed short, unsigned char, or unsigned short resp. for a conversion specification with an h or hh length modifier resp. is undefined behaviour, as the argument wasn't converted from type char, short, etc. resp. before.
  • I claim that the function operates in a well-defined manner for every value of type int and that printf behaves as if the parameter was converted to char, short, etc. resp. before conversion.
  • One could also claim that invoking the function with an argument that was not of the corresponding type before default argument promotion is undefined behaviour, but this seems abstruse.

Which of these three interpretations of §7.21.6.1¶7 (if at all) is correct?

1
I made the first claim (see linked answer). I actually do think that if we are pedantic with the words used in the paragraph my claim stands (the argument before promotion is a short variant) but the spirit of this paragraph/C makes FUZxxl's claim maybe more likely.ouah
I think the first approach is correct. The conversion routine may very well expect the limited range, thus use an optimized version (just think of a 8 or 16 bit CPU). Not sure, if the input argument has to be of the same type. Question is, how the format-string parser can tell a "true" int input argument from a short/etc. converted to int. Another aspect might be inlining by the compiler. At worst it might inhibit optimization, but nothing else, as it has to behave the same.too honest for this site
This line is somewhat unclear - %h......but its value shall be converted to short int or unsigned short int before printing) . So does it mean that it can hold values greater than unsigned short int /short int as it is undergone integer promotion ? And if so than value it hold is converted back to its original variable type ? What will be its outcome ?ameyCU
If you pass a 64 bit integer, and us %hhd, then you are going to hurt, because this will take up 8 bytes, and printf will expect it to take 4 (windows and Linux x86 anyway)Ben
@Ben Yes of course. I should have made clearer that I was talking about signed or unsigned int arguments, as these are the types expected for h and hh due to default argument promotions.fuz

1 Answers

4
votes

The standard specifies:

If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

[C2011 7.21.6.1/9]

What is meant by "the correct type", is conceivably open to interpretation, but the most plausible interpretation to me is the type that the conversion specification "applies to" as specified earlier in the same section, and as quoted, in part, in the question. I take the parenthetical comments about argument promotion to be acknowledging the ordinary argument-passing rules, and avoiding any implication of these functions being special cases. I do not take the parenthetic comments as relevant to determining the "correct type" of the argument.

What actually happens if you pass an argument of wider type than is correct for the conversion specification is a different question. I am inclined to believe that the C system is unlikely to be implemented by anybody such that it makes a difference whether a printf() argument is actually a char, or whether it is an int whose value is in the range of char. I assert, however, that it is valid behavior for the compiler to check argument type correspondence with the format, and to reject the program if there is a mismatch (because the required behavior in such a case is explicitly undefined).

On the other hand, I could certainly imagine printf() implementations that actually misbehave (print garbage, corrupt memory, eat your lunch) if the value of an argument is outside the range implied by the corresponding conversion specifier. This also is permissible on account of the behavior being undefined.