Unsigned integer print

Viewed 5363

Why does this print -1

#include <stdio.h>

int main(){
    unsigned int i = -1;
    printf("%d", i);
    return 0;
}

While this prints

#include <stdio.h>

int main(){
    unsigned int i = -1;
    printf("%u", i);
    return 0;
}

the largest possible integer value

Also, why does this

#include <stdio.h>

int main(){
    int c = printf("Hello");
    printf("%d", c);
    return 0;
}

Print hello5 and not 5.

Counter-statement //for Eric

You mentioned about side-effect, right?

#include <stdio.h>
int main(){
int i = 0;
for (; i++; printf("%d", i));
printf("%d", i);
return 0; 
}

Why does the printf ("%d",i) inside the for loop not print the value in this code as a side-effect?

7 Answers

Question 1

Why does this print -1…

unsigned int i=-1;
printf ("%d",i);

Formally, the behavior is not defined by the C standard, because i is an unsigned int but %d is for an int, and C 2018 7.21.6.1 9 says “If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.”

However, C has a history of conflating signed and unsigned types1, and many compilers tread lightly in this regard.2 Typically, what happens is:

  • The function call passes the unsigned int value of i by putting its bits in the place where an unsigned int argument should be passed.
  • The printf gets the bits for an int from the place where an int argument should be passed.
  • These are the same place.
  • In consequence, printf reinterprets the bits that represent an unsigned int as if they were an int.

In unsigned int i = -1, −1 is converted to unsigned int according to rules in the C standard (6.3.1.3, see below) that result in the maximum unsigned int value, which has all bits set in its representation: 1111…11112.

In reinterpreting those bits as int, most C implementations use two’s complement. In two’s complement, the bits 1111…11112 represent −1.

So printf takes the passed bits as the int value −1 and prints “-1”.

Question 2

While this prints…

unsigned int i=-1;
printf ("%u",i);

… the largest possible integer value

The rule for converting −1 to unsigned int is in 6.3.1.3 2:

… the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

“One more than the maximum value that can be represented in the new type” is 1+UINT_MAX. Adding −1 to 1 + UINT_MAX produces UINT_MAX, which is representable in unsigned int, so that is the value stored in i. Printing this with %u prints its value normally.

Question 3

Also, why does this…

int c=printf ("Hello");
printf ("%d",c);

… Print hello5 and not 5.

printf does two things:

  • It writes characters to standard output. (The C standard calls this a side effect because it is something the function does besides returning a value.)
  • It returns the number of characters written (or a negative value if an error occurred).

When you call printf("Hello"), it writes “Hello” to output and returns 5.

Then printf("%d", c) writes “5” to output.

The question suggests you think that using printf in an assignment or initialization causes only its return value to be used, suppressing the side effect. This is not so. Whenever an expression is evaluated in C, its side effect and main effect are evaluated.

(In fact, assignments are themselves expressions; c = printf("Hello") is an expression, and you can use it in further operations, such as x = 3 * (c = 5), which will assign 15 to x. We can take any expression and put a semicolon after it to make an expression statement, which evaluates the expression and discards the main result. printf("%d", c); evaluates printf for its side effect and discards the return value.)

Footnotes

1 For example, 6.5.2.2 6 allows substituting a signed integer type for the corresponding unsigned integer type and vice-versa in calls to functions without prototypes, albeit limited to values representable in both. For prototypes with ..., 7.16.1.1 allows the same substitution when using va_arg. The rules in 6.2.6.2 require value bits common to signed and unsigned types to have the same values.

2 I do not recall any formal statement in regard to conflating signed and unsigned types, but I know compiler designers take some care to avoid breaking code with certain legacy use or certain not uncommon use cases even if the C standard does not require supporting them. In C, it is easy to slip and pass an unsigned char to be printed with %u, which is technically wrong because the unsigned char will be promoted to int, not unsigned int.

On a system with 32-bit ints, both unsigned int -1 and signed int 2147483647 happen to have the same binary representation in memory (see Two's complement):

11111111 11111111 11111111 11111111

When passing the value to printf, the data type information is lost. Hence, printf relies on the format specifier (%d or %u) to determine whether the data was a signed or an unsigned integer.


About your second question: printf prints, regardless of whether you ignore the return value or not. Thus, the first line in your method prints Hello, the second one 5.

On the first question: It's the same representation in binary, but unsigned values won't use the last bit as a sign bit. @Heinzi's answer covers this more comprehensively.

Regarding

int c=printf ("Hello");
printf ("%d",c);

It prints Hello5 because you've simply called printf twice:

  1. Print hello and store the number of characters printed (5) in c.
  2. Print the value of c, which is 5.

When you assign -1 to an unsigned integer, it gets converted to a value representable in unsigned value using reduction modulo.

This

unsigned int i = -1;

is equivalent to

unsigned int i = UINT_MAX;

And when you print i with %d, it's actually undefined behaviour due to format mismatch.


Also, why does this [..] print hello5 and not 5.

The first printf call, int c=printf ("Hello");, still prints Hello and in the second you print the return value of previous printf's return value. Hence Hello5 output.

printf doesn't know the type that you defined. It only knows how to interpret the value by the format specifier. So in your first example, you use %d which tells printf to read this value as a signed int and print it accordingly. In the second case you use %u sou it tells printf to interpret it as unsigned int.

The largest possible unsigned value has the same binary representation as -1 for the same data type as signed, because the highest bit is interpreted as the sign.

Your your first dilemma, the answer is simple: printf uses the format specifier to decide on how to print the arguments passed to it, as stated by others as well.

For an unsigned int, the format is %u, while for a signed int, the format is %d.

See more information about the different format specifiers in the docs:

d, i

The int argument is converted to signed decimal notation.

o, u, x, X

The unsigned int argument is converted to unsigned octal (o), unsigned decimal (u), or unsigned hexadecimal (x and X) notation.

For your second dilemma:

Also, why does this

Print hello5 and not 5.

Because you told it to. There are two printf statements there. Assigning the result of printf does not "discard" the output of the printf itself -- it still prints out as instructed to. This (i.e. the I/O that functions like printf do) is called a side effect.

Your first printf indeed prints "Hello", while your second prints out the return value of your first printf (the number of characters printed):

https://man7.org/linux/man-pages/man3/printf.3.html#RETURN_VALUE

Upon successful return, these functions return the number of characters printed (excluding the null byte used to end output to strings).

For starters that's how integers work.

signed |             | unsigned
...    |  ...        | ...
 4     | 0000'0100b  | 4
 3     | 0000'0011b  | 3
 2     | 0000'0010b  | 2
 1     | 0000'0001b  | 1
 0     | 0000'0000b  | 0
-1     | 1111'1111b  | 255
-2     | 1111'1110b  | 254
-3     | 1111'1101b  | 253
-4     | 1111'1100b  | 252
...    | ...         | ...
Related