简体   繁体   中英

Promotions and conversions of variables in printf()

In this case,

#include <stdio.h>

int main()
{
    unsigned char a = 1;
    printf("%hhu", -a);

    return 0;
}

The argument -a in printf is promoted to int by the integer promotion by the unary minus operator and subsequently promoted by the default argument promotion and finally converted to unsigned char by the format specifier.
So -a => -(int)a (by ~ ) => no conversion by function call => (unsigned char)-(int)a (by %hhu ). Is my thought right?

You are correct that a is promoted to int in -a , and that printf("%hhu", -a); passes an int to printf . The notional conversion performed with %hhu is not clear.

Note that if a is not zero, then -a produces a value (in an int ) that is not an unsigned char value. Further, with two's complement eight-bit signed char , if a is greater than 128, then -a produces a value that is not a signed char value.

To understand %hhu , we look at the specification for u in C 2018 7.21.6.1 8:

The unsigned int argument is converted to unsigned octal (o), unsigned decimal (u),…

and for hh in 7.21.6.1 7:

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);…

First we have to resolve this issue of “ signed char or unsigned char ”. Does this say we can pass either a signed char or an unsigned char for %hhu ? I think not; I think the authors have just put together the language for %hhd (intended to convert a signed char ) and %hhu (intended to convert an unsigned char ). So I believe the intent is that a promoted unsigned char should be passed for the %hhu conversion specification.

Apple Clang 11.0.0 seems to agree, when passing -a (but not a ), it warns: “warning: format specifies type 'unsigned char' but the argument has type 'int' [-Wformat]”

As noted above, passing -a may pass a value that cannot result from passing a promoted unsigned char . It may even pass a value that cannot result from passing a promoted signed char or unsigned char . In this case, it can be argued we have violated the requirement to pass an unsigned char , and therefore the C standard does not specify the resulting behavior. Even though it says the passed value shall be converted to an unsigned char , I believe that is a notional conversion, not a specific requirement on the library implementation, and that is also falls under the “as if” rules: It does not actually have to be performed if the resulting defined behavior of programs is the same. But, since passing an improper value may not be defined, we do not have defined behavior.

That may be a strict reading of the rules, but it would not surprise me greatly if printf printed “4294967295” instead of “255” when a were 1.

printf is a variadic function. The type of the arguments passed by ... parameter are not known inside the function. As such, any variadic function must rely on other mechanisms to interpret the type of the va_arg s arguments. printf and family use a const char* format string to "tell them" what kind of arguments were passed. Passing a type different then the expected type as specified by it's format specifier results in Undefined Behavior.

For instance:

printf("%f", 24)

Is undefined behavior. There is no conversion from int to float anywhere because the arguments are passed as they are (after promotion) and inside the printf the function incorrectly treats its first argument as float . printf does not know and can't know that the real type of the argument is int .

Variadic arguments undergo some promotions of their own. Of interest for your question unsigned char is promoted to int or unsigned int (I am not sure tbo). As such there is no way for a variadic parameter to actually be of type unsigned char . So hhu while is indeed the specifier for unsigned char it will actually expect an unsigned int ( int ), which is what you pass to it.

So afaik the code is safe because of the two integer promotions caused by unary minus and passing variadic arguments. I am not 100% sure though. Integer promotions are weird and complicated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM