-1
int main(){
 char a = 5;
 float b = 6.0;
 int c = a + b;
 return c;
}

Looking at the generate instructions with gcc, the above code is evaluated like this:

  • Load 5 and convert it to float as a
  • Load 6 as b
  • Add a and b
  • Convert the result to an integer and return

Does gcc not care about the return type while it's dealing with the expression? It could have converted b to an int right off the bat as everything else is an integer type.

Is there a rule which explains how one side of an expression is evaluated first regardless of what the other side is?

Dan
  • 2,694
  • 1
  • 6
  • 19
  • 1
    Does this answer your question? [Implicit type promotion rules](https://stackoverflow.com/questions/46073295/implicit-type-promotion-rules) – 0x5453 Jun 23 '21 at 20:38
  • No im not talking about conversion rules, i'm asking about the order in which code is compiled to instructions. it seems like the right side of the expression is always evaluated first before the left side is even observed. – Dan Jun 23 '21 at 20:40
  • 1
    It can be compiled in any order or fashion to produce the expected result. – Eugene Sh. Jun 23 '21 at 20:41
  • 1
    @Dan there is no such a rule. Order of evaluation is not specified except the logical expressions. – 0___________ Jun 23 '21 at 20:41
  • Conversions are applied for each operator individually without regard to whether they exist in a larger expression. – dbush Jun 23 '21 at 20:42
  • Statements are always executed in sequence, so yes, the four statements within your program will certainly be executed in the order shown, top to bottom. – Steve Summit Jun 23 '21 at 20:42
  • @SteveSummit First and second can be reordered. – Eugene Sh. Jun 23 '21 at 20:43
  • Within an expression, the order of evaluation is generally unspecified. As long as an expression has at most one side effect, the order of evaluation doesn't matter. On the other hand, it's easy to accidentally write expressions which have multiple side effects and where the order matters. For example, see [this recent question](https://stackoverflow.com/questions/68024823). – Steve Summit Jun 23 '21 at 20:43
  • @0___________ so if the compiler wanted to turn `b` into an `int` first it's free to do so? there is no rule saying things should be done in order, as long as the output is correct? – Dan Jun 23 '21 at 20:43
  • @EugeneSh. I take the *as if* rule for granted, as I believe can any non language lawyer. – Steve Summit Jun 23 '21 at 20:44
  • 1
    @Dan the compiler is free to optimize it to `return 11;` alone without any conversions. – Eugene Sh. Jun 23 '21 at 20:44
  • 1
    @Dan If the result will be the same then yes – 0___________ Jun 23 '21 at 20:44
  • 1
    https://godbolt.org/z/MMcYvKnhs – 0___________ Jun 23 '21 at 20:45
  • @0___________ and why is `logical expressions` an exception here? – Dan Jun 23 '21 at 20:46
  • *it seems like the right side of the expression is always evaluated first before the left side is even observed* That's definitely not the case. A classic (counter) example is the undefined expression `a[i] = i++`. There's no telling whether the old or the new value of `i` is used to decide which element of `a[]` to store. It might be, or it might not be, as discussed in (among very many examples) [this recent question](https://stackoverflow.com/questions/68101787). – Steve Summit Jun 23 '21 at 20:47
  • @Dan because of the shorthand (or short circuit) evaluation. The logical expression will evaluate until its result is known. Then other operations will be skipped. Example: `if(1 || ++x)` x will not be incremented https://godbolt.org/z/f9PfP9Thn – 0___________ Jun 23 '21 at 20:47
  • @SteveSummit So it is not clear to me if the OP is interested in understanding the *abstract machine* flow, or the actual translation process here. – Eugene Sh. Jun 23 '21 at 20:48
  • @0___________ Oh right the `left to right` boolean evaluation rule. – Dan Jun 23 '21 at 20:50
  • 1
    @Dan Be aware that "order of evaluation" can be a slippery concept. When you say `a + b * c`, there's a rule that says the multiplication happens first. But when you say `f() + g() * h()`, there's no rule to say which of the three functions gets called irst. – Steve Summit Jun 23 '21 at 20:51
  • @SteveSummit interesting, even if the return type of these functions are integers, C still could apply them out of order? is there a link I can read more on this? – Dan Jun 23 '21 at 20:52
  • @Dan http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf – 0___________ Jun 23 '21 at 20:54
  • 1
    @Dan I discuss the `f() + g() * h()` example in [this answer](https://stackoverflow.com/questions/31087537/why-does-a-b-have-the-same-behavior-as-a-b/31088592#31088592). – Steve Summit Jun 23 '21 at 20:57
  • @SteveSummit Thank you. – Dan Jun 23 '21 at 20:57
  • Although an optimizing compiler can look ahead, in the abstract machine model, there's none of that. Another classic example is `double d = 1 / 3;`, where `d` ends up containing 0, and you can't fix it by doing `double d = (double)(1 / 3)`, either. – Steve Summit Jun 23 '21 at 21:07
  • @SteveSummit That makes sense,`(double)1 / 3` would do it. – Dan Jun 23 '21 at 21:12
  • Code provided has nothing to do with order of evaluation. It's all about type promotion. – Support Ukraine Jun 23 '21 at 21:28
  • " It could have converted b to an int right off the bat as everything else is an integer type" No, it couldn't. Consider ` char a = 5; float b = 6.5; int c = a * b;` The result is 32 but if it was done as you say it would give 30 (which is obviously wrong). – Support Ukraine Jun 23 '21 at 21:31
  • @4386427 I don't have multiplication in my example, that's a completely separate scenario. – Dan Jun 23 '21 at 21:32
  • @Dan No, it's not a completely separate scenario. You are arguing that because "everything else is an integer type" it could have converted the float to integer first. If you applied that "rule" to all arithmetic expression, things wouldn't work. And that's why it's all converted to float before the calculation – Support Ukraine Jun 23 '21 at 21:38

2 Answers2

3

You ask "Is there a rule?" Of course there is a rule. Any widely used programming language will have a huge set of rules.

You have an expression "a + b". a has type char, b has type float. There's a rule in the C language that the compiler has to find a common type and convert both to the common type. Since one of the values is a floating-point type, the common type must be a floating-point type, which is float, double, or long double. If you look closer at the rules, it turns out the common type must be float or double, and the compiler must document this. It seems the compiler chose "float" as the common type.

So a is converted from char to float, b is already float, both are added, the result has type float. And then there's a rule that to assign float to int, a conversion takes place according to very specific rules.

Any C compiler must follow these rules exactly. There is one exception: If the compiler can produce the results that it is supposed to produce then it doesn't matter how. As long as you can't distinguish it from the outside. So the compiler can change the whole code to "return 11;".

In the C language, partial expressions are evaluated without regard how they are used later. Whether a+b is assigned to an int, a char, a double, it is always evaluated in the same way. There are other languages with different rules, where the fact that a+b is assigned to an int could change how it is evaluated. C is not one of those languages.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • Re “If you look closer at the rules, it turns out the common type must be float or double, and the compiler must document this. It seems the compiler chose "float" as the common type”: There is no choice here, nor is the implementation required to document it. C 2018 6.3.1.8 requires that, if one operand is `float` and the other is an integer type, the integer operand be converted to `float`. – Eric Postpischil Jun 23 '21 at 22:25
0

If you change it to:

int main(){
 char a = 5;
 float b = 6.6;
 int c = a + 2*b;
 return c;
}

then it becomes clear that you have to keep the float 18.2 until the end.

With no optimizations, gcc acts as if this could happen and does a lot of conversions.

With just -O it already does the math itself and directly returns the final integer, even in above example.

There is no in-between reasoning and short-cut here. Why simplify from 5+6.0 to 5+6 but not to 11? Either act stupid and do cvtsi2ss xmm1,eax (and back etc.), or then tell them directly 11.