There are many things wrong with this program; it contains multiple forms of undefined behavior. I recommend to start compiling with these settings: What compiler options are recommended for beginners learning C?
First of all, most of these pointer assignments are invalid C - you cannot assign a pointer to one type to a pointer of a different, non-compatible type in C (see C17 6.5.16.1 and 6.5.4). You must use an explicit cast:
cc = (char*)&i;
.
Second, it is undefined behavior to use one particular conversion specifier in printf
but pass a variable by a different type. Similarly, you must print pointers using %p
and you should cast the pointer to void*
before you print it.
You should always place \n
at the end of each printf
call since on line buffered systems, \n
is often used to flush stdout and make things appear on the screen.
After correcting most of these cases of invalid C/undefined behavior/bugs except for still using wrong conversion specifiers, the code looks like this:
#include <stdio.h>
int main (void)
{
char c = 65, *cc;
int i = 0x12345678;
long l = 0x12345AB;
float f = 3.14;
cc = &c;
printf("c: %c, cc: %p\n", *cc, (void*)cc);
cc = (char*)&i;
printf("c: %d, cc: %p\n", *cc, (void*)cc);
cc = (char*)&l;
printf("c: %ld, cc: %p\n", *cc, (void*)cc);
cc = (char*)&f;
printf("c: %f, cc: %p\n", *cc, (void*)cc);
}
This code still invokes undefined behavior, so you are not guaranteed to get any particular result. When I run it on gcc x86_64 Linux I get the same output as you do however.
If we try to reason what the compiler does here, then in all cases the character you pass to printf
gets implicitly promoted to int
. This is because printf
is a "variadic function" and these functions come with a special rule of implicit argument promotion called "the default argument promotions". Meaning you might as well type (int)*cc
and you would get the same results.
On a little endian computer as is the case here, the character pointer points at the least significant byte of the larger types. That is 0x78, 0xAB and so on.
Now as it happens, the char
type is problematic for doing hardware-related programming like this, because it has implementation-defined signedness. Meaning that a compiler can let it either have the range -128 to 127 (2's complement) or 0 to 255. In your case it picked the former. A constant like 0xAB when read through the char*
will get treated as a negative value -85.
Then upon promotion to int
when passed to printf, this negative value gets "sign extended" - the compiler tries to preserve the decimal value -85. But since it is now stored in a 32 bit int
, the binary representation of that value is 0xFFFFFFAB
. If we would attempt to print that with %d
we'd get -85. If converting to unsigned int we would get 4294967211
.
But in case of %ld
and 8 byte data, the value 4294967211
fits inside one. The implicit promotion always goes to type int
. So a positive value is printed. Had you done an explicit conversion passed (long)*cc
to printf
instead, then it would have been sign extended as was done previously and it would print -85
.
As you can tell it's a bad idea to use char
or any signed types whenever dealing with raw data. Preferably use the unsigned integer types from stdint.h
instead. That is, uint8_t
to uint64_t
.