From which endpoint and how does C read the variables?

Viewed 99

I was messing around with pointers in C and was trying to read values from the same address using different types of pointers. First I created a double variable and assigned the number 26 to it. double g = 26; like so. And then I assigned g's address to a void pointer void *vptr = &g;. after that, I tried to read the value at the address of g as a float by type-casting. float *atr = (float*) (vptr);. When I tried to print the value of *atr it gave me 0.000000. Then i used pointer to a character since characters are 1 byte and tried to see values of those 8 bytes one by one.

char *t;
t = (char*) vptr;
for (int i = 0; i < 8; i++){
    printf("%x t[%d]: %d\n",t+i , i, t[i]);
}

it gave me this output

ffffcbe9 t[1]: 0
ffffcbea t[2]: 0
ffffcbeb t[3]: 0
ffffcbec t[4]: 0
ffffcbed t[5]: 0
ffffcbee t[6]: 58
ffffcbef t[7]: 64

Then I checked binary representation of g which is 01000000 00111010 00000000 00000000 00000000 00000000 00000000 00000000 using this website. When I convert every byte to decimal individually, first byte becomes 64 and the second is 58. So it was basically reversed. Then I tried to read as a float again but this time i shifted the address. atr = (float*) (vptr+4);. I didn't know how many bytes it would shift but coincidentally i discovered that it shifts by one just like char pointers. This time i printed as printf("%f\n",*atr); and now it gave me 2.906250. When I checked it's binary representation it was 01000000 00111010 00000000 00000000 which is the first half of the variable g. So I am kind of confused how C is reading values from addresses since it looks like c reads the values from right-end and when i add positive numbers to addresses it shifts towards left-end. I am sorry for any spelling or grammatical mistakes.

1 Answers

The order in which the bytes of an scalar object are stored in C are implementation-defined, per C 2018 6.2.6.1 2. (Array elements are of course stored in ascending order by index, and members of structures are in order of declaration, possibly with padding between them.)

The behavior of using *atr after float *atr = (float*) (vptr); is not defined by the C standard, due to the aliasing rules in C 2018 6.5 7. It is defined to examine the bytes through a char lvalue, as you did with t[i], although which bytes are which is implementation-defined per above.

A proper way to reinterpret some bytes of a double as a float is to copy them in byte-by-byte, which you can do with manual code using a char * or simply float f; memcpy(&f, &g, sizeof f);. (memcpy is specified to work as if by copying bytes, per C 2018 7.24.2.1 2.) This will of course only reinterpret the low-addressed bytes of the double as a float, which has two problems:

  • The low-address bytes may not be the ones that contain the most significant bytes of the double.
  • float and double commonly use different formats, and the difference is not simply that float has fewer bits in the significand (the fraction portion). The exponent field is also a different width and has a different encoding bias. So reinterpreting the double this way generally will not give you a float that has about the same value as a double.

I didn't know how many bytes it would shift but coincidentally i discovered that it shifts by one just like char pointers.

Supporting arithmetic on void * is a GCC extension that is, as far as I know, needless. When offsets are added to or subtracted from void *, GCC does arithmetic as if it were a char *. This appears to be needless because one can do the desired arithmetic simply by using a char * instead of a void *, so the extension does not provide any new function.

Related