Yes, it's technically UB. But which operation could any C compiler disallow in practice: converting from pointer to integer, arithmetic on integers, or converting from integer to pointer?
Yes, it's technically UB. But which operation could any C compiler disallow in practice: converting from pointer to integer, arithmetic on integers, or converting from integer to pointer?
int a[2], b[2];
int offset(void) { return b - a; }
int check(void) { return &a[1] + offset() == &b[1]; }
The function check() is optimised by GCC to return zero at -O1 optimisation level or higher, because it reasons that no matter how offset() is implemented, either the addition is undefined, or the comparison results in false.(Note that GCC does this even in a few cases where it is unclear whether the optimisation is valid. The example I provided is slightly more complicated than I would have liked to avoid that issue; the optimisation is definitely valid in this example.)
However, your code doesn't actually address my comment about conversions and arithmetic. Where does this code code fail?
int a[2], b[2];
uintptr_t x = (uintptr_t)a;
uintptr_t y = (uintptr_t)b;
uintptr_t offset = y - x;
int* p = (int*)(x + offset);
printf("b == p: %d\n", b==p);
I didn't try _every_ compiler on Godbolt, but I didn't see it misbehave anywhere I did try.edit: changed to uintptr_t
Yeah, but I did say: "which operation could any C compiler disallow in practice: converting from pointer to integer, arithmetic on integers, or converting from integer to pointer?"
If C/C++ compilers keep breaking pointer arithmetic in the game of exploiting undefined behavior for optimizations, people are going to start doing pointer arithmetic with integers when they need it. And they do need it sometimes, for debuggers, profilers, memory checkers, JITs, garbage collectors, shared memory, and so on.
If I had used `uintptr_t` instead of `ssize_t`, I think it's even compliant as far as wraparound goes.
edit: Note I changed the code to use uintptr_t
If I could, I would disallow integer to pointer---there are (in my opinion) better ways to address a hardware register than to do
unsigned char *uart = (unsigned char *)0xdeadbeef; /* [1] */
But I don't think it will go away any time soon in C (Over 30+ years later, and we're still a few years out from 1's compliment and signed magnitude integers being deprecated).[1] Here's one way using NASM:
global uart
absolute 0xdeadbeef
uart resb 1
Then in C code: extern unsigned char uart;Yeah, and I know that's why the C standard has so much slop in it. They want to keep the window of conformance open for past or future architectures.
> If I could, I would disallow integer to pointer [...]
It's not just about 0xDeadBeef hardware addresses, and I don't think anyone is talking about breaking assembly language.
However, C has unions and Rust has enums. You want those to share the same piece of memory in the fewest bytes you can get away with. Some interpreters, written in C, even use nan-tagging to store the important 48 bits of a pointer in the 52 bits of payload with double precision floats.
You don't have to like it, and you can discourage your children or coworkers from doing it, but there are legitimate reasons for all of this. The examples look ugly because they're short and stupid.
And nobody has even mentioned that `A[i] == (A + i) == (i + A) == i[A]` is going to continue to be valid in C. https://stackoverflow.com/a/381549