3 ms·
char p[1], q[1] = {0}; uintptr_t ip = (uintptr_t)(p+1); uintptr_t iq = (uintptr_t)q; if (iq == ip) { *(char*)iq = 10; print(q[0]); } I am genui
by James_K 2y ago
char p[1], q[1] = {0};
uintptr_t ip = (uintptr_t)(p+1);
uintptr_t iq = (uintptr_t)q;
if (iq == ip) {
*(char*)iq = 10;
print(q[0]);
}
I am genuinely a little scared of this C program.
- shakna 2y agoAs a C program, it doesn't actually work. [0] It's just C-as-pseudocode for LLVM IR. And LLVM has different semantics for what is allowed and denied, than C. [0] https://godbolt.org/z/58GcP1d9j https://godbolt.org/z/58GcP1d9j
- James_K 2y agoThe only reason it doesn't work is because the stack grows downwards on x86 targets so p is at a higher address than q. If you swap the variables round, it compiles and runs printing the value assigned. https://godbolt.org/z/bedb7cvY8 https://godbolt.org/z/bedb7cvY8
- shakna 2y agoAs the compiler is free to reorganise the variable order, that means you're relying on Undefined Behaviour for it to compile and do what you want. That's not something inherent to the way pointers work.
- James_K 2y agoIt is valid C program. Undefined behaviour occurs at runtime, but it is valid and should be compiled by a conformant C compiler.
- spaqin 2y ago[dead]
- chongli 2y agoSection 6.3.2.3 of the C11 standard [1] reads: 1 A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer. ... 6 Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type. The issue for your code is that on line 2 you cast to uintptr_t the pointer (p+1) to type char, which is not a pointer to void (and not a null pointer). So the result of your conversion is implementation-defined and the result need not be in the range of values of any integer type. Thus an implementation can assume that the result of the comparison (iq == ip) is always false and even omit the entire if-block. [1] http://port70.net/%7Ensz/c/c11/n1570.pdf http://port70.net/%7Ensz/c/c11/n1570.pdf
- James_K 2y ago> The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer: > uintptr_t Given this, are you suggesting that the code would be valid if the char pointer was first cast to void as so: char p[1], q[1] = {0}; uintptr_t ip = (void *)(p+1); uintptr_t iq = (void *)q; if (iq == ip) { *(char *)(void *)iq = 10; print(q[0]); } Additionally, it seems the code may be valid as-is: > A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. > § 6.2.5.28
- chongli 2y agoYou still have a few bugs to fix (need to cast to uintptr_t after casting to void *, need to use printf with a proper format string) but now your code is valid. I believe it's still implementation-defined because different implementations are free to lay out memory as they wish, say by including the use of padding bytes between values, or by changing the location of the stack and the direction it grows (up or down in memory). This means you cannot assume that the array q directly follows the array p in memory (they may be in the opposite order or they may be separated by padding bytes). I created a working version of your code on Godbolt Compiler Explorer [1]. I tried it with a few different compilers and the code runs. The comparison evaluates to false on every compiler that I could get to work at all. Some don't seem to print anything at all, though the assembly they generate seems like it ought to. As a general rule of thumb for C, you should assume it is impossible to reach the object pointed to by one pointer via another pointer, unless the second pointer directly aliases the first. You also cannot assume that the integer representation of a pointer corresponds directly to a valid address in memory. Virtual memory (with address translation), fat pointers, segmented memory, etc. are all possible which means the pointer's actual bit pattern should be considered an opaque structure handled by the implementation, not an integer for you to manipulate and then cast back to a pointer. The main reason for wanting to convert a pointer into a uintptr_t type is to serialize it into some other structure in a one-way fashion, such as to store it in a hash table. [1] https://godbolt.org/z/dT8G8o5xM https://godbolt.org/z/dT8G8o5xM
- varispeed 2y agoYou also wouldn't write such code in real world.