It is certainly not allowed, since it changes, as you have noted, the observeable behavior (different output) of the program (I won't go into the hypothetical case that veryLongComputation()
might not consume any measurable time -- given the function's name, is presumably not the case. But even if that was the case, it wouldn't really matter). You wouldn't expect that it is allowable to reorder fopen
and fwrite
, would you.
Both t0
and t1
are used in outputting t1-t0
. Therefore, the initializer expressions for both t0
and t1
must be executed, and doing so must follow all standard rules. The result of the function is used, so it is not possible to optimize out the function call, though it doesn't directly depend on t1
or vice versa, so one might naively be inclined to think that it's legal to move it around, why not. Maybe after the initialization of t1
, which doesn't depend on the calculation?
Indirectly, however, the result of t1
does of course depend on side effects by veryLongComputation()
(notably the computation taking time, if nothing else), which is exactly one of the reasons that there exist such a thing as "sequence point".
There are three "end of expression" sequence points (plus three "end of function" and "end of initializer" SPs), and at every sequence point it is guaranteed that all side effects of previous evaluations will have been performed, and no side effects from subsequent evaluations have yet been performed.
There is no way you can keep this promise if you move around the three statements, since the possible side effects of all functions called are not known. The compiler is only allowed to optimize if it can guarantee that it will keep the promise up. It can't, since the library functions are opaque, their code isn't available (nor is the code within veryLongComputation
, necessarily known in that translation unit).
Compilers do however sometimes have "special knowledge" about library functions, such as some functions will not return or may return twice (think exit
or setjmp
).
However, since every non-empty, non-trivial function (and veryLongComputation
is quite non-trivial from its name) will consume time, a compiler having "special knowledge" about the otherwise opaque clock
library function would in fact have to be explicitly disallowed from reordering calls around this one, knowing that doing so not only may, but will affect the results.
Now the interesting question is why does the compiler do this anyway? I can think of two possibilities. Maybe your code triggers a "looks like benchmark" heuristic and the compiler is trying to cheat, who knows. It wouldn't be the first time (think SPEC2000/179.art, or SunSpider for two historic examples). The other possibility would be that somewhere inside veryLongComputation()
, you inadvertedly invoke undefined behavior. In that case, the compiler's behavior would even be legal.