By void.pointer

2012-09-07 15:35:16 8 Comments

Possible Duplicate:
Unsequenced value computations (a.k.a sequence points)
Undefined Behavior and Sequence Points
Operator Precedence vs Order of Evaluation

I'm still trying to wrap my head around how the following expression results in undefined behavior:

a = a++;

Upon searching SO about this, I found the following question:

Difference between sequence points and operator precedence? 0_o

I read through all the answers but I still am having difficulty with the details. One of the answers describes the behavior of my above code example as ambiguous, in terms of how a is modified. For example, it could come down to either of these:


What exactly makes a's modification ambiguous? Does this have to do with CPU instructions on different platforms, and how the optimizer can take advantage of the undefined behavior? In other words, it seems undefined because of the generated assembler?

I don't see a reason for the compiler to use a=(a+1);a++;, it just looks quirky and doesn't make much sense. What would possess the compiler to make it behave this way?


Just to be clear, I do understand what is happening, I just don't understand how it can be undefined when there are rules on operator precedence (which essentially defines the order of evaluation of the expression). Assignment happens last in this case, so a++ needs to be evaluated first, to determine the value to assign to a. So what I expect is that a is modified first, during the post-fix increment, but then yields a value to assign back to a (second modification). But the rules for operator precedence seem to make the behavior very clear to me, I fail to find where there is any "wiggle-room" for it to have undefined behavior.


@Kerrek SB 2012-09-07 16:17:02

Let me run through the basic problem in the statement a = a++. We want to achieve all of the following things:

  • determine the value a (the return value of a++, #1)

  • increment a (the side effect of a++, #2)

  • assign the old value to a (effect of the assignment, #3)

There are two possible ways this could be sequenced:

  1. Store the original a into a (a no-op); then increment a. Same as a = a; ++a;. This is sequence #1-#3-#2.

  2. Evaluate a, increment a, assign the original value back to a. Same as b = a; ++a; a = b;. This is sequence #1-#2-#3.

Since there is no prescribed sequence, either of those operations are permissible. But they have a different ultimate result. Neither sequence is more natural than the other.

@Alex Brown 2012-09-07 15:58:32

That statement a=a++ has two results, and two assignments:


(because its a postincrement) And


These assignments clearly result in a different final value for a.

The drafters of the c standard did not indicate which of the two assignments should be written to a first and which second, so compiler writers are free to choose whichever they like, in any given situation.

The upshot is, it (this particular statement) won't crash, but your program can't rely on a having a specific value any more.

@Puppy 2012-09-07 15:50:46

The point here is that on some CPU architectures, like Intel Itanium, these two operations could be parallelized by the compiler at the instruction level- but making your construct well-defined would forbid that. At the time of sequence point specification, these architectures were mostly hypothetical, and since Itanium was a flop, it's well arguable that as of 2012, much of this is unnecessary complexity in the language. There's basically no possible disadvantage on any architecture still in common use- and even for Itanium, the performance advantage was minimal and the headache of writing a compiler that could even take advantage of it was huge.

Note also that in C++11, sequence points were replaced with sequenced before and sequenced after, which made more situations like this well-defined.

@supercat 2016-05-21 16:58:36

Even on modern machines, a possible issue is that if a and b are references that identify the same int, and x and y are int variables, code like a=b++; x=a; ...; y=b; may be evaluated as temp=b; a=temp; b=temp+1; x=temp; ... y=a;, behaving as though a spontaneously changes between the assignments to x and y. I'd like to see the Standard define an execution model which would recognize contagious non-determinism but stay on the rails, since the marginal benefits from allowing arbitrary behaviors beyond that are slight, but there would be usefulness to limiting...

@supercat 2016-05-21 17:02:41

...the consequences of overlapping assignments in cases where it doesn't matter what values get computed in certain edge cases provided that code stays on the rails. Requiring programmers to guard against such assignments even in those cases could make code less efficient than letting them yield arbitrary results.

@Nicol Bolas 2012-09-07 15:47:26

The first answer in the question you linked to explains exactly what's going on. I'll try to rephrase it to make it more clear.

Operator precedence defines the order of the computation of values via expressions. The result of the expression (a++) is well understood.

However, the modification of the variable a is not part of the expression. Yes, really. This is the part you're having trouble understanding, but that's simply how C and C++ define it.

Expressions result in values, but some expressions can have side effects. The expression a = 1 has a value of 1, but it also has the side effect of setting the variable a to 1. As far as how C and C++ define things, these are two different steps. Similarly, a++ has a value and a side-effect.

Sequence points define when side effects are visible to expressions that are evaluated after those sequence points. Operator precedence has nothing to do with sequence points. That's just how C/C++ defines things.

@void.pointer 2012-09-07 15:55:56

The separation of the processing of side effects and the evaluation of the expression is definitely what I'm having trouble with, because I just have a bad feeling that there is a situation out there where the value of the side-effect is dependent on the outcome of the expression, in which case the side-effects MUST be processed prior to the complete evaluation of the expression. Side effects are simply a modification of a block of memory somewhere. What if the same expression operates on that block of memory? Can't think of any examples off-hand that would do this, though.

@Nicol Bolas 2012-09-07 15:57:57

@RobertDailey: That's the point: if you write an expression, where the value computed by that expression is based on side effects that are not properly sequenced, you get undefined behavior. It's a two layer system: you need operator precedence, but the source values need to be sequenced to any side effects from prior expressions.

@jrok 2012-09-07 16:01:07

@Robert "What if the same expression operates on that block of memory". I think that's the point. C and C++ (for better of worse) don't define the order in which side effects take place, but they say when those side effects must be complete (by the next sequence point prior to C++11, before the next "sequenced after" operation in C++11). Hence the rule "don't modify something twice between two sequence points" to prevent ambiguities.

@void.pointer 2012-09-07 16:05:22

Why make it undefined though? That's my point. a = a++, why not require that side-effects are processed on-demand, as soon as evaluation reaches that point? Modify a to equal 1, then return 0, then finally assign 0 to a. I find no reason to make the behavior undefined. What were they thinking when they set it up this way in the standard?

@Nicol Bolas 2012-09-07 16:14:29

@RobertDailey: How it is undefined behavior is a different question from why it is. The latter is essentially speculative and probably has to do with giving compilers reasonable freedom of action and making their jobs easier. Or it may be some historical issue.

@Nicol Bolas 2012-09-07 16:16:56

@RobertDailey: Or maybe the spec writers were wise enough to know that endorsing code like a = a++ + ++a is a horrible idea ;)

@void.pointer 2012-09-07 16:18:40

@NicolBolas "horrible idea", is that your personal opinion? Where are the facts? :) I can't think of a use case that would make it horrible, or destructive, or whatever else. But, horrible in terms of readability and the fact that it doesn't make much sense to do so, I agree.

@Mike Seymour 2012-09-07 15:47:08

The precedence rules specify the order in which expressions are evaluated, but side effects do not have to happen during evaluation. They can be happen at any time before the next sequence point.

In this case, the side effect of the increment is sequenced neither before nor after the assignment, so the expression has undefined behaviour.

@David W 2012-09-07 15:43:24

This is probably too simplistic an explanation, but I think it is because there's no way to resolve when the code is "done" with "a". Is it done after the increment, or the assignment? The resolution ends up being circular. Assignment after the increment changes the semantics of when the incremented value is applied. That is, the code isn't done with "a" until "a" gets incremented, but a doesn't get incremented until the after the assignment is made. It's almost a language version of a deadlock.

As I said, I'm sure that's not a great "academic" explanation, but that's how I bottle it up between my own ears. Hope that's somehow helpful.

Related Questions

Sponsored Content

25 Answered Questions

[SOLVED] What is the "-->" operator in C++?

5 Answered Questions

[SOLVED] Undefined behavior and sequence points

4 Answered Questions

[SOLVED] What made i = i++ + 1; legal in C++17?

7 Answered Questions

11 Answered Questions

[SOLVED] Why is f(i = -1, i = -1) undefined behavior?

3 Answered Questions

[SOLVED] What is the difference between a sequence point and operator precedence?

2 Answered Questions

1 Answered Questions

5 Answered Questions

[SOLVED] Undefined behavior and sequence points reloaded

Sponsored Content