1

In the following code snippet that I stumbled upon the other day which passes a signed int to a function that expects an unsigned int

#include "stdio.h"

void set_data(unsigned int* addr, const unsigned int data)
{
    printf("Inside set_data:%d\n", data);
    *addr = data;
}

int main() {
    unsigned int a = 2;
    printf("Before set_data:%d\n", a);
    set_data(&a, -10);
    printf("After  set_data:%d\n", a);

    return 0;
}

OUTPUT:-
Note there is no warnings thrown by the compiler:-

zhossain@zhossain-linux1:~$ gcc -Wall test.c
zhossain@zhossain-linux1:~$ ./a.out
Before set_data:2
Inside set_data:-10
After  set_data:-10

Questions:-
1. Why is the compiler not shouting? implicit conversion?
2. How come "Inside set_data" prints a negative integer while data is clearly defined as unsigned int
3. Is this by any chance undefined behavior OR implementation defined which may not be a good practice to write safe portable code?

Looking forward to detailed explanation also would appreciate if you can point to relevant SO threads

Zakir
  • 2,222
  • 21
  • 31
  • Well because an `int` will always fit in `unsigned`, (2) you are telling `printf` to interpret the bits in `a` as an `int` (take a look at `twos-complement` storage of ints for further detail). The code is well-defined. – David C. Rankin Oct 02 '17 at 04:38
  • #2 `printf("Inside set_data:%d\n", data);` is UB when `data` > `INT_MAX`. – chux - Reinstate Monica Oct 02 '17 at 04:49
  • @chux - `printf()` has undefined behaviour whenever a format specifier specifies a different type than the corresponding argument. – Peter Oct 02 '17 at 05:25
  • @peter C11 §6.5.2.2 6 and §7.21.6.1 9 have a level of inconsistency that may be interpreted as `int/unsigned` values represented the same way with `printf()` lack indistinguishable with `"%u/%d"`. In this case, OP's `unsigned` value is certainly > `INT_MAX` and so UB. – chux - Reinstate Monica Oct 02 '17 at 14:31

2 Answers2

4

Looking at your code, I think I understand where your confusion is. You are passing -10 as unsigned value and you are scratching your head with "Why the hell is it printing -10 if it is an `unsigned value?"

It all boils down to bits. The bits for 10 are (using 16 bits to prevent a lot of zeros)

0000000000001010

In most computers negative values are stored in twos-complement format (which is layman terms is an inversion of all bits plus one). If applied twice you get the same number back. So, the twos-complement storage for -10 is:

1111111111110110

Which will happily fit in and unsigned value of the same type.

The next quandary is "How does it print -10?" As long as your value does not exceed the maximum value for int, "%d" will happily print the bits you provide as an integer. There is no problem in your code assigning -10 as int and then passing as unsigned, because you are passing the value 1111111111110110. You can print the value as signed int (-10) or unsigned int (4294967286).

That's really the only question you are answering for printf. What should it do with the bits?, "Do I print 11111111111111111111111111110110 as unsigned or do I print it as int. That's what you are telling printf with %u or %d.

There is no problem with Undefined Behavior as long as you do not attempt to use a value in a greater than INT_MAX as an int. Otherwise, you are fine.

Digest this, and let me know if I understood your concern, or if I missed the mark and I'm happy to help further.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
2

1) The compiler is not "shouting" (a more correct description is "issuing a diagnostic") about implicit conversions because the conversion from a signed type to unsigned type is well defined by the standard - it uses what is (mathematically) known as modulo arithmetic. The call of set_data(&a, -10) does such a conversion of -10 BEFORE calling set_data(). A value of -10 when converted to unsigned will result in a value of UINT_MAX - 9 where UINT_MAX is obtained from <limits.h> and represents the largest value a unsigned int can represent. Compilers can often be configured to give warnings about such conversions but, by default, most compilers are configured NOT to. If you want warnings, you'll need to read documentation for your compiler to work out how.

2) All of your calls of printf() result in undefined behaviour, since %d informs printf() that the corresponding argument is of type int, and you have passed an unsigned (i.e. a type mismatch). With your particular implementation, it appears that conversion of values of type int to or from unsigned does not change the bitwise representation, and the unsigned value passed to printf() has the same bitwise representation as an int with value -10. That is NOT guaranteed by the standard.

3) As I've said in the previous point, your call of printf() has undefined behaviour, due to a type mismatch. Change the format specifier to %u to get well-defined behaviour (and you will then see the value printed will be equal to UINT_MAX-9. The conversion of an int with a negative value to unsigned - which occurs when calling set_data() in main() has implementation-defined behaviour since the value of UINT_MAX is implementation-defined. In other words, the value that results from converting -10 from int to unsigned is implementation-defined.

Note, also, that the const specifier on the second parameter of set_data() is redundant, as far as the caller is concerned, since that argument is passed by value. Even without the const, any changes the function makes to data will only be visible within the body of set_data() and invisible to the caller.

I'm not going to give links to other SO threads, since my answer is reasonably complete without them. You can search for such links for yourself.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • Thanks for the explanation - regarding the `const` specifier for the data, I prefer conveying a message to ppl reading/working on this function later - this parameter is not to be messed with - should be written as is.. – Zakir Oct 02 '17 at 20:00
  • Passing by value already conveys that message - as far as the caller is concerned, the value won't change. Passing by function arguments by value is a foundational concept in C. – Peter Oct 03 '17 at 08:03