ccastingbitwise-operatorstwos-complementones-complement

Type casting ~0 to (unsigned int) and (unsigned short) with format specifier %d and %u produces same result for short but different result for int


On my way to computing ranges of char, short, int, and long variables, both signed and unsigned, I took help of the following solutions:

  1. https://stackoverflow.com/a/19085193/11320006

  2. http://www.learntosolveit.com/cprogramming/Ex_2.1_cal_limits.html

According to solution 1, I expected (unsigned short)~0 in the code below to output -1 and 65535, assuming its behaviour is the same as (unsigned int)~0 code for the two format specifiers.

// the two statements below produce different results
printf("Value of unsigned int is %d\n", (unsigned int)~0); // outputs -1                                                                                                                                                     
printf("Value of unsigned int is %u\n", (unsigned int)~0); // outputs 4294967295

// whereas, the two statements below produce the same result. Why?
printf("Value of short unsigned int is %d\n", (unsigned short)~0); // outputs 65535, expected -1
printf("Value short unsigned int is %u\n", (unsigned short)~0); // outputs 65535

Why is there a difference in the behaviour of (unsigned short)~0 and (unsigned int)~0?


Solution

  • Why is there a difference in the behaviour of (unsigned short)~0 and (unsigned int)~0?

    The behavior of those expressions is analogous. Supposing two's complement representation of type int, each computes the largest representable value of its (unsigned) type.

    However, variable arguments to a variadic function such as printf are subject to default argument promotions. This affects unsigned short, promoting it to int if int can represent all unsigned short values, as is the case in your example (and to unsigned int otherwise). It does not affect arguments of type int or unsigned int, or wider integer types.

    The key problem with the code presented is that ...

    printf("Value of unsigned int is %d\n", (unsigned int)~0);
    

    ... exhibits undefined behavior on account of the %d directive not being correctly type matched to the corresponding argument. %d must be matched to a signed int, but you have associated it with an unsigned int. In practice, the UB is manifesting as interpreting the bit pattern of the argument as if it were a signed int instead of an unsigned one, but in principle, the program could have done anything at all within its power.

    Note well that this also has undefined behavior because of type mismatch:

    printf("Value short unsigned int is %u\n", (unsigned short)~0);
    

    The directive %hu would be the best match to the corresponding actual argument, but %d is acceptable because of the aforementioned automatic type promotion. %u does not match. In this case, however, the UB manifested is identical to the behavior you expected -- at least as far as the output indicates. In practice, the bit pattern of the non-negative signed int argument has been interpreted as an unsigned int.