(Just a short post to break the “dry spell”.)
One interesting consequence of C having non-pure functions is that every function you call can be accessing your local variables if pointers to them “escape”.
If we compile the following C code with -O3
extern void f(int *a); extern void g(void); int main(void) { int a = 0; f(0); a++; g(); a++; g(); a++; return a; }
we get machine code where the additions are collapsed and a
is not even given a memory position:
main: push {r3, lr} movs r0, #0 bl f(int*) bl g() bl g() movs r0, #3 pop {r3, pc}
But if we pass a pointer to the local variable to a single function,
extern void f(int *a); extern void g(void); int main(void) { int a = 0; f(&a); a++; g(); a++; g(); a++; return a; }
we get a load/store after every function call:
main: push {lr} sub sp, sp, #12 add r0, sp, #8 movs r3, #0 str r3, [r0, #-4]! bl f(int*) ldr r3, [sp, #4] adds r3, r3, #1 str r3, [sp, #4] bl g() ldr r3, [sp, #4] adds r3, r3, #1 str r3, [sp, #4] bl g() ldr r0, [sp, #4] adds r0, r0, #1 add sp, sp, #12 ldr pc, [sp], #4
Once a pointer to a variable has escaped, the optimizer has to assume that it’s part of the global state and every function call could be modifying it.