4 ms·
Section 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 b
by chongli 2y ago
Section 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
- James_K 2y agoThis code doesn't assume that q follows p, that's the trick to it. The way you are trying to explain this to me, I feel like you haven't read the article or understood the code snippet.
- chongli 2y agoAfter (re)reading the article I've come to the conclusion that with proper provenance tracking through the cast to an integer, the comparison: (iq == ip) Should be replaced (at compile time) with the constant 0 (or false, if you like). Since iq and ip have different provenances -- even though they may (or may not) be bitwise equal (which is entirely implementation-dependent in C) -- they can never be considered equal, so falsity can be deduced at compile time. It should also be considered invalid (and a compile-time error) to cast any old plain integer (one whose provenance is not a pointer) to a pointer, regardless of whether or not the address of said pointer lands on a live object, because an integer created by any means other than a cast from a pointer should have empty provenance. Edit: Ahhh, this provenance story is way messier than I thought. One serious problem with tracking provenance is that functions like memcpy become a huge headache to deal with.