int i = 0;
*p = 10;
return i;
// becomes
*p = 10;
return 0;
aren't valid, because p could "just be a number" pointing to i's stack slot. int *p = rand();
or that code could be in a function int f(int *p)
which may be called from completely different compilation units (or even across a dynamic library boundary) meaning there's no way for the compiler to know what p is.As a specific example, consider the difference between these two versions of my example above:
int f() {
int i = 0;
*(&i) = 10;
return i;
}
and the original: int g(int *p) {
int i = 0;
*p = 10;
return i;
}
If a call like g(rand()) ends up with p == &i, the unoptimized versions of that and of an f() call execute exactly the same sequence of memory accesses, and yet the optimization I mention above only applies to g. In essence, the source of the number "&i" seems to matter.> others are performed only if the pointer is specified via keywords and other annotations to have a very special meaning.
Some of this already exists, such as 'restrict'.
On the contrary, I feel those concepts are great for reasoning about code on a symbolic level. Which is what a compiler is all about.
> In this declination a pointer is just a number, the problem is that certain programming statements in C/C++ that...
Wouldn't this mean the compiler will need to generate a more memory loads and stores that could otherwise be proven redundant? It'll need to assume a store through a pointer 'a' at index unknown at compilation time can modify data at pointer 'b', even though it could otherwise deduce there's no aliasing.
The compiler needs semantic data to answer questions like "can an access through pointer A access data visible through pointers B and C?"
If it doesn't know that, it'll need to generate those redundant memory accesses.
Before storing (or loading) through A, it'll need to store temporary values previously written to B and C.
After a store through pointer A, it might need to load B and C values again for more computation, because a store to A might have modified them.