user3195703
user3195703

Reputation: 31

Unexpected static variable address behaviour

This is C code snapshot:

int* f(int x) {
    static int y;
    y = x * x;
    return &y;
}

float* g(float x) {
    static float y;
    y = x * x;
    return &y;
}

int main(void) {
    printf("*f(1)=%d\n", *f(1));
    printf("*f(2)=%d\n", *f(2));
    printf("*f(1) + *f(2)=%d\n", *f(1) + *f(2));
    printf("*g(1.0)=%f\n", *g(1.0));
    printf("*g(2.0)=%f\n", *g(2.0));
    printf("*g(1.0) + *g(2.0)=%f\n", *g(1.0) + *g(2.0));
    return 0;
}

The output is:

*f(1)=1
*f(2)=4
*f(1) + *f(2)=5
*g(1.0)=1.000000
*g(2.0)=4.000000
*g(1.0) + *g(2.0)=8.000000

And I don´t really understand the dual behaviour from f() and g(). First, I suspected that this was a compiler issue, but either BCC or GCC provide the same output.

Shouldn´t *f(1) + *f(2) output be equal to *g(1.0) + g(2.0)? (Either 5 5.0 or 8 8.0)

Upvotes: 3

Views: 93

Answers (1)

RageD
RageD

Reputation: 6823

I believe Oli is correct. To be more explicit, this is going to depend on how the value is stored before the addition occurs. If you execute *g(1.0), then *g(2.0) before storing the value, you will add 4.0 + 4.0 = 8.0 (remember, each pointer points to the address of the same static variable). Otherwise, if you execute *g(1.0) and store its value in a register then execute *g(2.0) and add the results, you will get 1.0 + 4.0 = 5.0.

So, really, this depends on how the compiler writes this into machine code. Consider the following pieces of pseudo-x86 assembly (for simplicity we use int's instead of float's):

push 1 
call g ; First call to g(1);
add esp, 4 ; Pop value 1
mov ebx, eax ; Save our pointer
push 2
call g ; Call to g(2)
add esp, 4 ; Pop value 2 -- Remember that eax == ebx, now (they point to same address)
mov eax, [eax] ; Forget the pointer, store the value (4).
mov ebx, [ebx] ; Do the same thing, value is 4 since they point to same place
add eax, ebx   ; Add our two values. 4 + 4 = 8
ret

Conversely, consider the following

push 1 
call g ; First call to g(1);
add esp, 4 ; Pop value 1
mov ebx, [eax] ; Save the value at the pointer (1).
push 2
call g ; Call to g(2)
add esp, 4 ; Pop value 2 -- Remember that eax == ebx, now (they point to same address)
mov eax, [eax] ; Forget the pointer, store the value (4).
add eax, ebx   ; Add our two values. 4 + 1 = 5
ret

So order of instructions really matters when using a shared variable like this without explicitly storing its value. Typically, the instruction order will be compiler-dependent and whether certain optimization flags are turned on or off. Further, either result could be argued as a reasonable assumption to make with no hard-semantics governing this since it is not making any real violations of the standard (for more: see Eric's response below): it is dereferencing the return values from each function and adding the results. Consequently, if a compiler optimization reorders the way things are done, this causes unexpected results.

Upvotes: 4

Related Questions