size_t size_int = sizeof(unsigned long int);
size_t size_ptr = sizeof(void*);
printf("sizeof(unsigned long int): %zu\n", size_int);
printf("sizeof(void*): %zu\n", size_ptr);
if(size_int == size_ptr) {
int a = 0;
void * ptr_a = &a;
// case 1
unsigned long int case_1 = *((unsigned long int*)&ptr_a);
printf("case 1: %lu\n", case_1);
// case 2
unsigned long int case_2 = (unsigned long int)ptr_a;
printf("case 2: %lu\n", case_2);
// case 3
unsigned long int case_3 = 0;
memcpy(&case_3, &ptr_a, sizeof(void*));
printf("case 3: %lu\n", case_3);
// case 4
void *ptr_b = NULL;
memcpy(&ptr_b, &case_3, sizeof(void*));
int *ptr_c = (int*)ptr_b;
*ptr_c = 5;
printf("case 5: %i\n", a);
}
In fact I am aware that there are uintptr_t and intptr_t in C99. However, for educational purposes I want to ask some questions. Before I start, I know that it is a bad practice and should never be done in this way.
Q1. Could case 1 cause an undefined behaviour? Is it safe? If it is not, why? If it is safe, is it guaranteed that "case_1" variable holds exactly the same address as an unsigned long int?
Q2. Same above for case 2.
Q3. Same above for case 3.
Q4. Same above for case 4.
Ignoring the pointer vs integer size concern, this is still undefined behavior because of strict aliasing violation. What is the strict aliasing rule? The memory location where a
void*object resides cannot get de-referenced as anunsigned long. This can in turn cause incorrectly generated machine code during optimization etc, particularly when the code is divided across multiple translation units. So this is valid concern and not just theoretical "language-lawyering".There might also, at least theoretically, be undefined behavior due to issues with alignment. In practice, I don't really see how alignment will be an issue in case the pointers and integers hold the same size.
Even more theoretically, there might be trap representations, either in the
unsigned long(which would in turn requires an exotic 1's complement or signed magnitude system), or in the pointer type itself. Some hardware might have trap representations for certain addresses and in theory you could get a hardware exception on such systems, though probably just when going from integer to pointer.This is well-defined - we can always converts from pointers to integers and back. But again there's the issue with object sizes and potentially also with alignment - especially when going from integers to pointers.
Apart from the same size and alignment concerns, this is valid code. And C doesn't place any requirements on the binary representation of pointers, that's beyond the scope of the standard.
Same concerns as in 3).