1

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.

snow monster
  • 91
  • 1
  • 11
  • 3
    That order is not specified. If you're constructing a compiler, you're free to choose. – klutt Mar 08 '23 at 11:31
  • Isn't it dependent on the compiler you are using. – xihtyM Mar 08 '23 at 11:32
  • In the case of gcc for example – snow monster Mar 08 '23 at 11:34
  • 1
    Related (not duplicate): [c - Why are these constructs using pre and post-increment undefined behavior? - Stack Overflow](https://stackoverflow.com/questions/949433/why-are-these-constructs-using-pre-and-post-increment-undefined-behavior) – MikeCAT Mar 08 '23 at 11:35
  • 3
    @snowmonster gcc doesn't make any such guarantees. The best you could say is that, for a specific version of gcc, at a specific optimization level, compiling for a specific architecture, it *happens* to evaluate this first, then that, etc. It's not something you should rely on, If you really want to know, then look at the assembly code it produces. Just don't expect it not to change. – Tom Karzes Mar 08 '23 at 11:37
  • 1
    If one operation depends on another then the one with highest priority will be evaluated first otherwise it's not specified and doesn't matter. – Fredrik Mar 08 '23 at 11:41
  • 1
    @snowmonster you can rely on the order of evaluation. It is not specified and you need also read about **sequence points** – 0___________ Mar 08 '23 at 11:51
  • 1
    On gcc it solves the first parenthesis and then the second one, you can test it with godbolt: https://godbolt.org/z/KvM7d1qjq , just select the text `(x*x + r*r)` and `(y*y + w*w)` with the mouse and see how it highlight the sections in the second panel. – David Ranieri Mar 08 '23 at 11:52
  • @DavidRanieri does it matter? – 0___________ Mar 08 '23 at 11:52
  • 1
    @0___________ What do you mean? OP wants to know what happens in gcc (3rd comment) – David Ranieri Mar 08 '23 at 11:54
  • @klutt https://en.cppreference.com/w/c/language/operator_precedence – Fredrik Mar 08 '23 at 11:56
  • 1
    @DavidRanieri Not necessarily: https://godbolt.org/z/TnE5zvqzd – Ian Abbott Mar 08 '23 at 11:59
  • 1
    @DavidRanieri if you enable optimizations it behaves different way: https://godbolt.org/z/nbs4rbfda So - it does not matter as it may change with options or/and compiler version. BTW IMO `-O0` examples have a little or no sense – 0___________ Mar 08 '23 at 12:00
  • @0___________ yes, and you can set those flags in godbolt – David Ranieri Mar 08 '23 at 12:05
  • @Fredrik Yes I know about precedence, but I fail to see the connection to "if one operation depends on another" – klutt Mar 08 '23 at 12:05
  • 2
    @DavidRanieri The point is that you cannot trust gcc to do it in a certain order. It might flip things. – klutt Mar 08 '23 at 12:07
  • @klutt of course, I did not say it is always like that, I just showed him how to examine a concrete case :) the title is _How does a C compiler read **this** operation?_ – David Ranieri Mar 08 '23 at 12:11
  • @IanAbbott as far as I can see is solving the first parenthesis first also in your snippet – David Ranieri Mar 08 '23 at 12:27
  • @DavidRanieri No, the evaluation is being interleaved. It is doing the four multiplications (two multiplications in each parenthetised sub-expression) before it does either of the additions. – Ian Abbott Mar 08 '23 at 12:47
  • @IanAbbott good point, I stand corrected. – David Ranieri Mar 08 '23 at 12:53
  • 1
    @DavidRanieri Yes, but your answer is still a bit misleading. What is true is that *that specific version of gcc* with those *specific compiler flags* on this *specific platform* did it in that order for *that specific code*. – klutt Mar 08 '23 at 12:53
  • 1
    @klutt Totally agree ... I should have pointed all this out, it was a bit of a vague answer :) – David Ranieri Mar 08 '23 at 12:59
  • 1
    Short answer: Within each pair of parentheses, the multiplications happen before the additions (though we can't say which multiplication happens first). And then, because of the parentheses, the additions happen before the subtraction. Beyond that there's nothing we can say. – Steve Summit Mar 08 '23 at 13:48
  • 2
    @klutt In case of `a() + b() * c()` we can't know the execution order of the functions, but we can know that `b` and `c()` have been executed before the multiplication. Whereas `a()` may or may not have been executed at that point. And that's about as far as operator precedence matters when it comes to order of execution. – Lundin Mar 08 '23 at 15:46
  • @Lundin Ah, that makes sense. I thought it meant something completely different. – klutt Mar 08 '23 at 16:56

2 Answers2

3

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
3

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:

  • The compiler might do things in any way it pleases.
  • The compiler need not document how it does things.
  • The compiler need not behave consistently throughout the program, even when encountering several identical sections of code.
  • The programmer should not expect any certain outcome or write a program that relies on it.

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.

Lundin
  • 195,001
  • 40
  • 254
  • 396