3 ms·
> 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
by 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.