cpointersmemorycastingdereference

Why does this C code print different values for the same pointer when cast to different types?


#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr;

    printf("Pointer as int*: %p\n", (void*)ptr);
    printf("Pointer as char*: %p\n", (void*)(char*)ptr);
    printf("Pointer as float*: %p\n", (void*)(float*)ptr);
}

Output:

Pointer as int*: 0x7ffee4b5c9a0
Pointer as char*: 0x7ffee4b5c9a0
Pointer as float*: 0x7ffee4b5c9a0

Observation:

The output shows the same address for all three pointer types. However, if I modify the code slightly to dereference the pointers, the values differ:

#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr;

    printf("Value as int*: %d\n", *ptr);
    printf("Value as char*: %d\n", *(char*)ptr);
    printf("Value as float*: %f\n", *(float*)ptr);
}

Output:

Value as int*: 1
Value as char*: 1
Value as float*: 0.000000

Why does dereferencing the same pointer cast to different types produce different values, and how does pointer type casting work in C?


Solution

  • C allows all manner of wild & crazy pointer conversions. C23 6.3.3.3:

    A pointer to void can be converted to or from a pointer to any object type. A pointer to any object type can be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

    A pointer to an object type can be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.

    So unless there's a problem with alignment for the pointed-at type, you can cast back and forth between most object pointers. In order to live up to the above quoted parts, the compiler is very likely to use the same pointer format for all object pointers, even though it is not explicitly required. So therefore you get the same addresses even after casting.

    However, just because the pointer conversion was allowed, that doesn't mean that de-referencing the pointer as a different, possibly non-compatible type is ok. It is undefined behavior most of the time: there could be problems with alignment, representation and the type system itself (What is the strict aliasing rule?).

    For example *(float*)ptr is definitely undefined behavior because of strict aliasing violations. But it could also be undefined behavior in case the floating point format has trap representations or in case float and int have different alignment requirements.

    *(char*)ptr is ok though, thanks to a special exception allowing us to inspect any object in C by de-referencing it as if it was a character array. This is also specified in C23 6.3.3.3:

    When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.

    (But the other way around is not ok - going from a char[] and de-referencing as *(int*) would invoke strict aliasing undefined behavior and maybe also misalignment undefined behavior.)

    Keep in mind however that *(char*)ptr is not portable. On a 32 bit int little endian system, the int will have a layout of 01 00 00 00 and so the result is 1. But on a similar big endian system, the data layout will be 00 00 00 01 and the result is then 0. So you aren't getting 1 because that's the value the int contains, but because that's the contents of the least significant byte.

    int arr[] = {257, 2, 3, 4, 5}; ... printf("Value as char*: %d\n", *(char*)ptr); will also give 1 on a little endian system, since the raw value is then (hex) 01 01 00 00.