Why can't the C compiler optimize changing the value of a const pointer assuming that two pointers to the same variable would be illegal/UB?
Why can't the C Compiler optimize the first line away, under the assumption, that two pointers to the same variable would be illegal/UB?
Because you haven't instructed the C compiler to do so -- that it is allowed to make that assumption.
C has a type qualifier for exactly this called restrict
which roughly means: this pointer does not overlap with other pointers (not exactly, but play along).
The assembly output for
bool f(int* restrict a, const int* b) {
*a = 2;
int ret = *b;
*a = 3;
return ret != 0;
}
is
mov eax, DWORD PTR [rsi]
mov DWORD PTR [rdi], 3
test eax, eax
setne al
ret
... which removes the assignment *a = 2
From https://en.wikipedia.org/wiki/Restrict
In the C programming language, restrict is a keyword that can be used in pointer declarations. By adding this type qualifier, a programmer hints to the compiler that for the lifetime of the pointer, only the pointer itself or a value directly derived from it (such as pointer + 1) will be used to access the object to which it points.
The function int f(int *a, const int *b);
promises to not change the contents of b
through that pointer... It makes no promises regarding access to variables through the a
pointer.
If a
and b
point to the same object, changing it through a
is legal (provided the underlying object is modifiable, of course).
Example:
int val = 0;
f(&val, &val);
While the other answers mention the C side, it is still worth taking a look at the Rust side. With Rust the code you have is probably this:
fn f(a:&mut i32, b:&i32)->bool{
*a = 2;
let ret = *b;
*a = 3;
return ret != 0;
}
The function takes in two references, one mutable, one not. References are pointers that are guaranteed to be valid for reads, and mutable references are also guaranteed to be unique, so it gets optimized to
cmp dword ptr [rsi], 0
mov dword ptr [rdi], 3
setne al
ret
However, Rust also has raw pointers that are equivalent to C's pointers and make no such guarantees. The following function, which takes in raw pointers:
unsafe fn g(a:*mut i32, b:*const i32)->bool{
*a = 2;
let ret = *b;
*a = 3;
return ret != 0;
}
misses out on the optimization and compiles to this:
mov dword ptr [rdi], 2
cmp dword ptr [rsi], 0
mov dword ptr [rdi], 3
setne al
ret
Godbolt Link