1
vec[ival++] <= vec[ival]

This expression has undefined behavior, because the order of evaluation of operator's (<=) operands is undefined.

How can we rewrite that expression to avoid the undefined behavior? I've found an answer that appears to work:

vec[ival] <= vec[ival + 1]

If that is the right way to avoid the undefined behavior, why does rewriting it that way avoid the undefined behavior?

Adding any reference about how to fix that expression would be great.

Shafik Yaghmour
  • 143,425
  • 33
  • 399
  • 682
  • You're missing up two concepts here. _Order of evaluation_ is _unspecified_. However, _unspecified_ behavior is still limited by what the language allows. Here, there are only a finite number of orderings, and the actual order must be one of those. _Undefined Behavior_ is far worse. The language puts no constraints on that. – MSalters Oct 30 '18 at 13:54
  • 1
    BTW, did you just pick a random C++ version or do you specifically mean the two-versions-ago version of C++. – MSalters Oct 30 '18 at 14:02
  • @MSalters I hate the way some questions are tagged with a version of a language or tool for no specified reason. Version tags can still be useful, but they need to be used for a well specified reason. – curiousguy Oct 30 '18 at 16:34
  • As I quote below, it is undefined because you have an unsequnced side effect and read of the same memory location. You can have unspecified order of evaluation but no undefined behavior [this question is a great example](https://stackoverflow.com/q/33598938/1708801) – Shafik Yaghmour Oct 30 '18 at 16:52
  • @MSalters Using C++11 even now is not exactly crazy – Lightness Races in Orbit Oct 30 '18 at 17:11
  • Before you re-write it you have to determine what the original author was trying to achieve. Its not obvious what they were trying to do so there are several ways you can write this each with different results. – Martin York Oct 30 '18 at 18:08

4 Answers4

2

Yes, your first example has undefined behavior because we have an unsequenced modification and access of a memory location, which is undefined behavior. This is covered in draft C++ standard [intro.execution]p10 (emphasis mine):

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [ Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. — end note  ] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a memory location ([intro.memory]) is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location, and they are not potentially concurrent ([intro.multithread]), the behavior is undefined. [ Note: The next subclause imposes similar, but more complex restrictions on potentially concurrent computations. — end note  ] [ Example:

void g(int i) {
  i = 7, i++, i++;              // i becomes 9

  i = i++ + 1;                  // the value of i is incremented
  i = i++ + i;                  // the behavior is undefined
  i = i + 1;                    // the value of i is incremented
}

— end example  ]

If we check out the section of relational operators which covers <= [expr.rel] it does not specify an order of evaluation, so we are covered by intro.exection and thus we have undefined behavior.

Having unspecified order of evaluation is not sufficient for undefined behavior as the example in Order of evaluation of assignment statement in C++ demonstrates.

Your second example avoids the undefined behavior since you are not introducing a side effect to ival, you are just reading the memory location twice.

I believe that is a reasonable way to solve the problem, it is readable and not surprising. An alternate method could include introducing a second variable, e.g. index and prev_index. It is hard to come with a fast rule given such a small code snippet.

Deduplicator
  • 41,806
  • 6
  • 61
  • 104
Shafik Yaghmour
  • 143,425
  • 33
  • 399
  • 682
0

It avoids undefined behavior because you are not changing the value of ival. The issue you're seeing in the first sample is that we can't determine what the values of ival are at the times that they're used. In the second sample, there's no confusion.

jwismar
  • 11,772
  • 3
  • 28
  • 42
0

Let's start with the worst problem first, and that is the Undefined Behavior. C++ uses sequencing rules. Statements are executed in seqeuence. Usually that's top-to-bottom, but if statements, for statements, function calls and the like can change that.

Now withing a statement there still might be a further sequence of execution, but I'm very intentionally writing might. By defaults, the various parts of a single statement are not sequenced relative to each other. That's why you can get the varying order of execution. But worse, if you change and use a single object without sequencing, you have Undefined Behavior. That is bad - anything might happen.

The proposed solution (iVal + 1) doesn't change iVal anymore, but generates a new value. That is entirely safe. Still, it leaves iVal unchanged.

You may want to check on std::adjacent_find(). Chances are that the loop you're trying to write is already in the Standard Library.

MSalters
  • 159,923
  • 8
  • 140
  • 320
0

The first problem is that as the initial code exhibited undefined behavior, under the C++ standard there is no "fix". The behavior of that line of code is not specified by the C++ standard; to know what it is supposed to do you have to have another source of information.

Formally, that expression can be rewritten as system("format c:"), as the C++ standard does not mandate any behavior from a program that exhibits undefined behavior.

But in practice, when you run into something like that, you have to read the original programmer's mind.

Well, you can solve anything with lambdas.

[&]{ bool ret = vec[ival] <= vec[ival+1]; ++ival; return ret; }()

Second,

vec[ival] <= vec[ival+1]

is not the same, because it lacks the side effect of ival being 1 greater after the expression is evaluated.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465