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