3

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)

4
  • This is either unspecified or undefined behaviour; there is no well-defined evaluation order here. Commented Jan 14, 2014 at 21:30
  • 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 value 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. Commented Jan 14, 2014 at 21:33
  • related question from the C faq; basically, you need to put the function calls in separate statements if you want to guarantee what order they are called in. Commented Jan 14, 2014 at 21:41
  • I think this may be undefined behavior in C 2011 but not in C 1999. As is widely known, in C 1999 the behavior is undefined if an expression both modifies and separately uses the value of an object between sequence points. But that does not happen here, because there are sequence points at function calls and the ends of full expressions within the calls. So it is merely unspecified in which order the calls occur. In C 2011, 6.5 2 says the behavior is undefined if a side effect is unsequenced relative to a different side effect or a value computation. That would seem to apply to this code. Commented Jan 14, 2014 at 21:52

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

There may be a real violation in C 2011. Clause 6.5, paragraph 2, seems to say the behavior is undefined if different side effects on an object are unsequenced relative to each other, and the side effects of assigning to the static objects are unsequenced relative to each other (for the same object) because the order of function call evaluation is not determined.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.