27

I vaguely remember reading somewhere that it is undefined behaviour if multiple operands in a compound expression modify the same object.

I believe an example of this UB is shown in the code below however I've compiled on g++, clang++ and visual studio and all of them print out the same values and can't seem to produce unpredictable values in different compilers.

#include <iostream>

int a( int& lhs ) { lhs -= 4; return lhs; }
int b( int& lhs ) { lhs *= 7; return lhs; }
int c( int& lhs ) { lhs += 1; return lhs; }
int d( int& lhs ) { lhs += 2; return lhs; }
int e( int& lhs ) { lhs *= 3; return lhs; }

int main( int argc, char **argv )
{
    int i = 100;
    int j = ( b( i ) + c( i ) ) * e( i ) / a( i ) * d( i );

    std::cout << i << ", " << j << std::endl;

    return 0;
}

Is this behaviour undefined or have I somehow conjured up a description of supposed UB that is not actually undefined?

I would be grateful if someone could post an example of this UB and maybe even point me to where in the C++ standard that it says it is UB.

ctor
  • 5,938
  • 2
  • 26
  • 37

3 Answers3

34

No. It is not. Undefined behavior is out of question here (assuming the int arithmetic does not overflow): all modifications of i are isolated by sequence points (using C++03 terminology). There's a sequence point at the entrance to each function and there's a sequence point at the exit.

The behavior is unspecified here.

Your code actually follows the same pattern as the classic example often used to illustrate the difference between undefined and unspecified behavior. Consider this

int i = 1;
int j = ++i * ++i;

People will often claim that in this example the "result does not depend on the order of evaluation and therefore j must always be 6". This is an invalid claim, since the behavior is undefined.

However in this example

int inc(int &i) { return ++i; }

int i = 1;
int j = inc(i) * inc(i);

the behavior is formally only unspecified. Namely, the order of evaluation is unspecified. However, since the result of the expression does not depend on the order of evaluation at all, j is guaranteed to always end up as 6. This is an example of how generally dangerous unspecified behavior combination can lead to perfectly defined result.

In your case the result of your expression does critically depend on the order of evaluation, which means that the result will be unpredictable. Yet, there's no undefined behavior here, i.e. the program is not allowed to format your hard drive. It is only allowed to produce unpredictable result in j.

P.S. Again, it might turn out that some of the evaluation scenarios for your expression lead to signed integer overflow (I haven't analyzed them all), which by itself triggers undefined behavior. So, there's still a potential for unspecified behavior leading to undefined behavior in your expression. But this is probably not what your question is about.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 1
    I suppose it's still unspecified behavior? – Pubby Dec 22 '12 at 17:54
  • 2
    @GManNickG But there's no sequence point between the function calls. If you call multiple functions that all modify the same variable, without a sequence point between the function calls, isn't that still UB? Or at the very least unspecified because there's no guarantees about which function gets called first? – sepp2k Dec 22 '12 at 17:54
  • 1
    @sepp2k: Every modification is followed by sequence point (in fact more than one sequence points in the function itself ). For example, `;` introduces sequence point in the function. Also, there is sequence points before and after a function call. – Nawaz Dec 22 '12 at 17:56
  • 2
    @David: There's no need for sequence point in `b( i ) + c( i )` because it by itself makes no modifications to `i`. Sequence points exist is at the entry and at the exist of each function, which makes sure that each actual modification of `i` is surrounded by sequence points and thus isolated from each other. – AnT stands with Russia Dec 22 '12 at 17:57
  • @sepp2k: Deleting my comment since it's been added to the answer, but no: to call one function, you must introduce a sequence point, which means, since all modifications happen in functions (in this example), all modifications have an intervening sequence point. – GManNickG Dec 22 '12 at 17:58
  • @GManNickG Okay, I see that now. But it *is* unspecified. And unlike the example in this answer, the OP's example has multiple valid results (even if all the compilers he tried it on produces the same one). – sepp2k Dec 22 '12 at 18:02
  • @AndreyT, is there a list of all the sequence points in either the 98 or 11 standard? – ctor Dec 22 '12 at 18:06
  • @AndreyT, Yeah my question wasn't about integer overflow, I dare say in some of the many possibilities there will at some point be an overflow but my question was more to do with sequence points as I was going on the basis that I knew `int j = ++i * ++i;` was UB. – ctor Dec 22 '12 at 18:09
12

No its not undefined behavior.

But it does invoke unspecified behavior.

This is because the order that sub-expressions are evaluated is unspecified.

int j = ( b( i ) + c( i ) ) * e( i ) / a( i ) * d( i );

In the above expression the sub expressions:

b(i)
c(i)
e(i)
a(i)
d(i)

Can be evaluated in any order. Because they all have side-effects the results will depend on this order.

If you divide up the expression into all sub-expressions (this is pseudo-code)
Then you can see any ordering required. Not only can the above expressions be done in any order, potentially they can be interleaved with the higher level sub-expressions (with only a few constraits).

tmp_1 = b(i)           // A
tmp_2 = c(i)           // B
tmp_3 = e(i)           // C
tmp_4 = a(i)           // D
tmp_5 = d(i)           // E

tmp_6 = tmp_1 + tmp_2  // F   (Happens after A and B)
tmp_7 = tmp_6 * tmp_3  // G   (Happens after C and F)
tmp_8 = tmp_7 / tmp_4  // H   (Happens after D and G)
tmp_9 = tmp_8 * tmp_5  // I   (Happens after E and H)

int j = tmp_9;         // J   (Happens after I)
Martin York
  • 257,169
  • 86
  • 333
  • 562
8

It isn't undefined behavior but it has unspecified results: The only modified object is i through the references passed to the functions. However, the call to the functions introduce sequence points (I don't have the C++ 2011 with me: they are called something different there), i.e. there is no problem of multiple changes within an expression causing undefined behavior.

However, the order in which the expression is evaluated isn't specified. As a result you may get different results if the order of the evaluation changes. This isn't undefined behavior: The result is one of all possible orders of evaluation. Undefined behavior means that the program can behave in any way it wants, including producing the "expected" (expected by the programmer) results for the expression in question while currupting all other data.

user
  • 6,897
  • 8
  • 43
  • 79
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 1
    The important C++11 quote here is: "Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function." This makes it unspecified behaviour, rather than undefined. – Joseph Mansfield Dec 22 '12 at 18:02