user2793162
user2793162

Reputation:

Printf behaviour and casting

in this code:

char t = 200;
printf("%u \n",t);
printf("%u \n",(unsigned char)t);

output is this:

4294967240

200

Why first printf prints some random value? Was %u specifier alone not enough to interpret the number as unsigned? and print 200? Why does the cast help in the second case? What is happening under the hood?

Upvotes: 1

Views: 192

Answers (4)

Kaslai
Kaslai

Reputation: 2505

Inside of a variable argument function, all integral types that are passed in are automatically promoted to the longest integral type that the system supports. This means that single byte char types are promoted to 32 bit or 64 bit types on modern x86 computers when passed into a function like printf. I know for a fact that GCC's char (and evidently whatever compiler you're using, too) defaults to being signed, so your value of 200 is actually -56 in a 2's compliment signed integer scheme.


Let's first take a look at why first casting it to an unsigned char prints 200 like expected.

The binary representation for the value 200 is 11001000. That value is 8 bits wide, and you set a signed 8 bit type to this value.

In 2's compliment arithmetic, that same binary value actually represents -56.

Casting that byte to an unsigned type would still be 11001000, but it would change the meaning of it back to 200.

If you promote that to a 32 bit value using unsigned arithmetic, that would become 00000000 00000000 00000000 11001000

That 32 bit value still represents the value 200, and printing it as an unsigned value will indeed show 200.


Now let's look at the case where you aren't casting it.

The binary representation for the value 200 is 11001000. That value is 8 bits wide, and you set a signed 8 bit type to this value.

In 2's compliment arithmetic, that same binary value actually represents -56.

If you promote that to a 32 bit value using 2's compliment arithmetic, that would become the binary value 11111111 11111111 11111111 11001000

That 32 bit value still represents -56 in 2's compliment arithmetic. However, by printing it as an unsigned integer, you've made the compiler reinterpret a 32 bit signed value as a 32 bit unsigned value. For any non-negative signed value, this works as expected. However, all of those leading 1s in the 2's compliment negative value now represent a very large unsigned type.


If I punch that 32 bit representation of -56 into a binary-to-unsigned-decimal converter, I get the value 4,294,967,240 - which is the value you said you were getting by calling printf("%u", -52);

In the case of printf, you're really expected to know how to treat the data that's being passed in. All that printf sees in the argument list is a long binary blob; it doesn't even know how many arguments there are. The format string is supposed to tell printf how to interpret that binary blob of data. There's absolutely no type checking that printf can perform. If you tell it to read an integer from the binary blob, it will try to render the next sizeof(int) bytes as an integer, even if the binary data is actually intended to represent a float.

In this case, you've passed in a single signed char to printf, which caused the binary blob to contain contain a signed 32 bit integer, and you've told printf that it's actually an unsigned 32 bit integer. There's no way for printf to tell that it should actually be a signed char.

Upvotes: 0

haccks
haccks

Reputation: 106022

The C standard doesn't specify whether ordinary char is a signed or an unsigned type. Some compilers treat it as a signed while others treat it as unsigned type.

It seems that char is signed by default on your implementation. signed char can hold value between -128 to 127 only.

Why first printf prints some random value?

The reason that you are getting random value is because of undefined behavior of the program.

Was %u specifier alone not enough to interpret the number as unsigned? and print 200?

No. No type promotion inside printf function. C11: 7.29.2 p(9):

If a conversion specification is invalid, the behavior is undefined.335)

If you declare

double f = 5.7;

and use %d to print f, then you might expect that you will get an int value.

printf("%d", f) // Undefined behavior.  

but it will invoke undefined behavior.

Why does the cast help in the second case? What is happening under the hood?

It helps in nothing. Once you get undefined behavior, all bets are off.

Upvotes: 7

drum
drum

Reputation: 5651

That is because char is a byte. 200 in binary is 11001000. int is at least 16 bits wide. When you cast %u, it becomes unsigned int.

4294967240 in binary is 11111111111111111111111111001000.

11111111-11111111-11111111-11001000

If you look at the last 2 bytes, that is the same as 200 in binary.

This is assuming OP is using x86 system.

Upvotes: -1

Tim Čas
Tim Čas

Reputation: 10867

There are a few things going on here.

  1. char on your system is signed by default (this is implementation-defined, it just happens to be so on your system)
  2. the above means it can only hold values -128 to +127 (assuming x86 architecture), so this:

    /*signed*/ char t = 200;
    

    ... is actually interpreted as this:

    char t = (signed char)200; /* actual value: -56 */
    

    so that's why t is negative.

  3. when you pass in t to printf(), it gets promoted to a signed int, whilst maintaining its value (-56)
    • the promotion is a property of variadic functions (printf being one of them; see note below)
    • however, printf() is expecting an unsigned int (because you're using %u and not %d), and thus interprets the integer as that (signed -56 and unsigned 4294967240 have the same representation in x86)
  4. casting t to unsigned char actually does two things:
    • it converts the -56 back to +200 (since -56 cannot be represented by a signed char); this is because of the unsigned char
    • the +200 is extended to fill an unsigned int. This is because printf is a variadic function

(* note: for an explaination and examples of variadic functions in C, see this WP entry: http://en.wikipedia.org/wiki/Variadic_function#Example_in_C)

(* note2: I've simplified some things here for the same of brevity and understanding; C standard does not, for example, require that char is 8-bit --- but it is in most modern systems)

Upvotes: 2

Related Questions