These issues are why various compiler hints exist.
In the dead code example, the gcc function attribute 'const' can be applied to the declaration of bar(), telling the compiler that it is a pure function whose result depends on nothing but its arguments.
In the pointer example, the C99 standard 'restrict' qualifier can be applied to a, b and c to tell the compiler that the values pointed to by these variables do not overlap.
'restrict' will also help the global variable example - the reason that N is loaded each time around the loop is because as far as the compiler knows, one of the a[i]s could alias with N.
Mild quibble: the attribute you want is "pure", not "const". The distinction is that a const function inspects nothing but its arguments, but a pure function is allowed to read (but not write) external memory. Both are without side effects and can be optimized out of loops, but pure is looser. Not all const functions can be pure.
"pure" in D causes the compiler to disallow, within that function, writes or reads from global mutable data. This inevitably follows from the principle that if you pass the same arguments to a pure function, you'll get the same result returned.
A pure function is still allowed to allocate memory, though, and throw an exception.
A subtle point that bit me, const functions should not read from pointer arguments, such as const char pointers. The pointed to data is external (to the const function) memory.
Also, gcc does not seem to warn about reading eternal memory. It seems like this would be an easy error for the compiler to detect.
Yes, that's why I said "...whose result depends on nothing but its arguments." - the example bar() function in the original article does not read global memory, so it can be declared __attribute__((const)).
Sure. I was just pointing out that that is a stricter constraint than required for the optimization in question. Doing loop hoisting and CSE wants "pure" functions, because pure "means" a function without side effects.
The "meaning" of const is that the function depends on nothing but its arguments, and can therefore have its value computed at compile time, or be part of a global CSE pass. That's a different optimization.
It seems to me that loop hoisting would also be easier with ((const)), because to do so with a ((pure)) function requires further assessing that the loop does not modify any global state that might be visible to the ((pure)) function. A ((const)) function can be hoisted out of a loop even if the loop modifies globals, or values through pointers that might point at globals.
In the dead code example, the gcc function attribute 'const' can be applied to the declaration of bar(), telling the compiler that it is a pure function whose result depends on nothing but its arguments.
In the pointer example, the C99 standard 'restrict' qualifier can be applied to a, b and c to tell the compiler that the values pointed to by these variables do not overlap.
'restrict' will also help the global variable example - the reason that N is loaded each time around the loop is because as far as the compiler knows, one of the a[i]s could alias with N.