How does a C compiler read this operation i.e. what is the order of the operations?
z = (x*x + r*r) - (y*y + w*w)
I'm confused whether it starts with the left parentheses first or it works on both at the same time.
How does a C compiler read this operation i.e. what is the order of the operations?
z = (x*x + r*r) - (y*y + w*w)
I'm confused whether it starts with the left parentheses first or it works on both at the same time.
The C standard does not specify the order of evaluation for most “structurally independent” operations. There might not even be any order of operations in the sense that one operation might be started in part, then another operation might be started in part, then the first operation might be completed, then the second. (For example, a compiler could implement 64-bit integer arithmetic using sequences of 32-bit operations.)
In the expression you give, z = (x*x + r*r) - (y*y + w*w)
, the order of operations on the right side of the =
does not matter if x
, r
, y
, and w
are ordinary variables—loading the value of x
from memory will not affect loading the value of r
or the other variables, so it does not matter which of these are done first. However, if these variables are qualified with volatile
or are replaced with expressions (such as function calls with side effects, such as printing to standard output), then the order can matter. In these cases, the C standard does not say which of them is evaluated first. The compiler can evaluate the second r
first, then the first x
, then the first w
, then the second y
, and so on.
There are some structural dependencies in the expression. The result of x*x
must be evaluated before it can be added to the result of r*r
, and the result of x*x + r*r
must be evaluated before the result of y*y + w*w
can be subtracted from it.
In practice, a compiler is likely to evaluate expressions using values it has on hand already. For example, using ordinary unqualified variables, if a recent prior statement were q = y*y - w*w
, a good compiler would like retain the value of y*y
and w*w
for reuse in z = (x*x + r*r) - (y*y + w*w)
. So y
, w
, y*y
, and w*w
would be evaluated before x
, r
, x*x
, and r*r
. Conversely, a different prior statement could result in x
, r
, x*x
, and r*r
being evaluated earlier, and other combinations are also possible.
In most situations, a compiler may evaluate the sub expressions in any order it pleases, just as soon as all evaluations are done at the point when the value is to be used.
The old C99 standard explained this best, 6.5:
(The operators explicitly mentioned have special order of evaluation rules.)
Except as specified later (for the function-call
()
,&&
,||
,?:
, and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified.
This is unspecified behavior, which means:
In this specific case the programmer shouldn't expect a certain order in which the operands are evaluated/executed.
The usual way to illustrate this is to replace arithmetic operands with function calls. Taking your little equation, we can cook up a function with a name corresponding to each of those variables. Then do some "mocking" with dirty macros:
#include <stdio.h>
int x (void) { puts(__func__); return 1; }
int r (void) { puts(__func__); return 2; }
int y (void) { puts(__func__); return 3; }
int w (void) { puts(__func__); return 3; }
int* z (void){ puts(__func__); static int foo; return &foo; }
#define x x()
#define r r()
#define y y()
#define w w()
#define z *z()
int main (void)
{
z = (x*x + r*r) - (y*y + w*w);
}
Here each operand in the expression results in a function call. The name of the function called will be printed. And when you try this out on multiple compilers or the same one with different optimization options, you'll find out that they may behave differently.
In the case of gcc for example
It is still unspecified. Check out this example using gcc 12.2 vs gcc 5.1 with identical compiler options: https://godbolt.org/z/haPYefnb1
We should note that the assignment operator =
in C requires the right operand to be evaluated before the value is updated and yet gcc 5.1 chose to execute z
first. What it did was to store down the address returned, to use it later. It's free to do so, this is conforming behavior.