5

I have read this article: Undefined behavior and sequence points, but i cannot figure, whether it is UB, or not.

Consider, following example:

#include <iostream>
class op {
public:
  explicit op(int x) {
    std::cout << "x:   " << x << std::endl;
  }

  op & operator + (const op & /* other */) {
    return *this;
  }
};

int main(int /* argc */, char * /* argv */ []) {
  int x = 0;
  op o = op(x++) + op(x++) + op(x++);

  std::cout << "res: " << x << std::endl;

  return 0;
}

I expect output like this (or some permutation of output based on order of evaluation):

x:   0
x:   1
x:   2
res: 3

gcc-4.7.1 and clang-3.0 gives me such output, but when i compile this example with msvc-2010 i've got output:

x:   0
x:   0
x:   0
res: 3

Can you give me some information about this behaviour.

Community
  • 1
  • 1
Alexey
  • 918
  • 5
  • 13
  • 1
    If you are getting different results on different compilers, that sure smells like UB, doesn't it? – SingerOfTheFall Nov 12 '12 at 12:39
  • 3
    The fact that you are getting different results in different compilers is usually a strong indication of Undefined Behavior (i.e. if the standard provided a definition of the result, then --bugs aside-- all compilers would yield the same output. – David Rodríguez - dribeas Nov 12 '12 at 12:39
  • @DavidRodríguez-dribeas: `sizeof(x)` isn't UB ;-p. It takes a fair understanding of this part of the standard to establish that this code really does have UB, different results on different compilers don't prove that. It is not merely implementation-defined or unspecified, and those two things form a middle-ground between "standard defines the result" and "UB". – Steve Jessop Nov 12 '12 at 13:19

2 Answers2

10

The order of arguments evaluation in a + b + c is compiler-specific. Thus, the order of calls to x++ will be compiler-specific and relaying on it will be undefined behavior.

Using x++ or++x in such expressions is usually a sign of bad coding standard. It is better to avoid it and simplify your expressions.

In this question Compilers and argument order of evaluation in C++ you can find a discussion on the order of arguments evaluation in C++.

Here is the explanation of C++ evaluation order with the references to C++ standard: http://en.cppreference.com/w/cpp/language/eval_order

P.S. Bjarne Stroustrup says it explicitly in "The C++ Programming Language" 3rd edition section 6.2.2. He also gives a reason:

Better code can be generated in the absence of restrictions on expression evaluation order.

(from https://stackoverflow.com/a/2934909/1065190)

Community
  • 1
  • 1
Sergey K.
  • 23,426
  • 13
  • 95
  • 167
  • I was also downvoted for no reason (AFAIK)... sometimes downvotes don't make sense... better get used to it. – Luchian Grigore Nov 12 '12 at 12:48
  • @Luchian Grigore: it happens :( – Sergey K. Nov 12 '12 at 12:49
  • +1 if its any consolation I up-voted both of you. well thought answers. The down-voter here may have missed the meaning of "argument calculation" but thats his problem; your answer is still solid (as is Luchian's). – WhozCraig Nov 12 '12 at 12:50
  • @WhozCraig: i have changed ``calculation`` to ``evaluation``. That should be more in compliance with the C++ standard. – Sergey K. Nov 12 '12 at 12:52
  • 2
    As i understand, compiler may reorder instructions for different optimizations (http://stackoverflow.com/a/621556/734072), so it can put 3 `"load"` instructions, and after that perform increment of `x` – Alexey Nov 12 '12 at 12:53
  • @Alexey: yes, and the standard does not force the compiler to any particular order. – Sergey K. Nov 12 '12 at 12:54
  • 2
    I didn't downvote, but it's not just relying on the order of evaluation that results in undefined behavior. The code has UB even if you don't care what the order of evaluation is. The reason is that *any* code which has a permitted evaluation order for which `x` is modified twice between sequence points, has UB. This code has such a permitted evaluation order because the implementation is permitted to evaluate the three `x++` sub-expressions first, then start evaluating the `op` constructors and `op::operator+` calls that introduce sequence points. – Steve Jessop Nov 12 '12 at 13:15
  • @Steve Jessop: sure, that's why i added 2 links to another questions. There are a good discussions there related to this topic. – Sergey K. Nov 12 '12 at 13:18
  • @SteveJessop You gave excellent definition: `The reason is that any code which has a permitted evaluation order for which x is modified twice between sequence points, has UB`. – Alexey Nov 12 '12 at 13:23
9

It's undefined behavior because there's no sequence point between the post-increments of x. You can't tell which + will evaluate first, you can't tell which op(x++) will be constructed first, and you can't tell in which order x++ will be executed. It's undefined, just don't write code like that.

Luchian Grigore
  • 236,802
  • 53
  • 428
  • 594