Multiple preincrement operations on a variable in C++(C ?)
Asked Answered
K

2

15

Why does the following compile in C++?

int phew = 53;
++++++++++phew ;

The same code fails in C, why?

Keever answered 11/9, 2010 at 7:5 Comment(2)
Adding C++0x tag just for fun. :)Whitcomb
There must be a standard question we can reference for this type of question. So all questions (that we get this time of year from new college students) can just be quickly closed and marked read this.Albertina
W
15

That is because in C++ pre-increment operator returns an lvalue and it requires its operand to be an lvalue.

++++++++++phew ; in interpreted as ++(++(++(++(++phew))))

However your code invokes Undefined Behaviour because you are trying to modify the value of phew more than once between two sequence points.

In C, pre-increment operator returns an rvalue and requires its operand to be an lvalue. So your code doesn't compile in C mode.

Whitcomb answered 11/9, 2010 at 7:7 Comment(11)
@Prasoon: Not to second guess you, I am just curious to read about what you said; "you are trying to modify the value of phew more than once between two sequence points". Can you provide an annotation to this part of the standard so I can read more about this?Rms
@Merlyn Morgan-Graham : Read this article by Steve Summit: c-faq.com/expr/seqpoints.html .Whitcomb
Commentary This specification only needs to be followed exactly when there is more than one access between sequence points, or for objects declared with the volatile storage class. As far as the developer is concerned, a modification occurs. As long as the implementation delivers the expected result, it is free to do whatever it likes.Whitcomb
Just to be clear, ++++i is well-defined for user-defined types, right?Skillful
@FredOverflow: Yes because function call introduces a sequence point. :)Whitcomb
I cannot quote the standard, but I believe that the behavior is well defined. The standard requires an specific order of execution. Consider that you had int & f( int & x ) { return ++x; } and the call: int x = 0; f( f( f( x ) ) ); The inner f must be executed before the middle f and that is guaranteed to be performed before the external f call. The semantics if pre-increment require the increment to be performed before the value is yielded. On the other hand, int x = 0; int y = ++x + x++; is undefined as the relative order of execution of the pre and post increment undefined.Billy
@David: §5/4: "Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression." I think Prasoon is right, although it would almost be hard to write a compiler so bad as to get it wrong.Bouchard
@Potatoswatter: I'd guess that the simple mistake you could make in writing the compiler would be to inline the operator++, and then to apply optimisations to the result as if it were a single expression, without somehow annotating the inlined code to retain the sequence point. It's a fairly fundamental error, since if you got it wrong in this case I bet the optimiser would incorrectly coalesce/reorder statements in other cases too, but I think it's an omission rather than a comission...Semivitreous
This is only UB in C++03 though. It's valid in C++0x.Goode
@Johannes: What is changing in C++0x that makes this no longer UB?Elongate
@Daniel see my answer for an explanationGoode
G
28

Note: The two defect reports DR#637 and DR#222 are important to understand the below's behavior rationale.


For explanation, in C++0x there are value computations and side effects. A side effect for example is an assigment, and a value computation is determining what an lvalue refers to or reading the value out of an lvalue. Note that C++0x has no sequence points anymore and this stuff is worded in terms of "sequenced before" / "sequenced after". And it is stated that

If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

++v is equivalent to v += 1 which is equivalent to v = v + 1 (except that v is only evaluated once). This yields to ++ (v = v + 1) which I will write as inc = inc + 1, where inc refers to the lvalue result of v = v + 1.

In C++0x ++ ++v is not undefined behavior because for a = b the assignment is sequenced after value computation of b and a, but before value computation of the assignment expression. It follows that the asignment in v = v + 1 is sequenced before value computation of inc. And the assignment in inc = inc + 1 is sequenced after value computation of inc. In the end, both assignments will thus be sequenced, and there is no undefined behavior.

Goode answered 11/9, 2010 at 15:4 Comment(21)
Good answer. Similarly int a=4; ++a=5; would not invoke UB in C++0x, right?Whitcomb
@Prasoon yes. It would set a to 5. Likewise a = ++a; will not be UB, but a = a++; will be UB.Goode
@Johannes : Cool :-). The world is changing.Whitcomb
@Johannes : What about int a=4,b; b = a + a++ ;, that would be UB or not. Lets see, addition of a and a++ is sequenced before assignment, but hey order of evaluation is still unspecified, is it?. Phew! this is confusing.Whitcomb
@Johannes: I don't understand why a = ++a does not invoke undefined behavior, but a = a++ does. What happened to "except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified"?Elongate
@Daniel, the second does invoke undefined behavior because the modification to a in "a++" is not sequenced before, but after the value computation of "a++" (naturally, because you want it to yield the old value). Thus the assignment and the modification in "a++" is not sequenced relative to each other. The text you quoted is now worded as "Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced."Goode
@Prasoon, the modification to a in "a++" is sequenced after value computation of "a++". The two a's in "a + a++" are not sequenced in any way, neither their value computations, nor their side effects. So you have a side effect (modification to a) that is unsequenced relative to a value computation to that a (the first operand), and so you have UB.Goode
a = a++; the steps involved are a) value computation of lvalue of 'a' (lhs) b) value computation of lvalue of 'a' (rhs), c) side effect of ++, d) side effect of assignment. Do you mean that in C++0x steps (d) and (c) are unsequenced?Bland
But given a = ++a, since (c) happens before (d), the two side effects are sequenced on the same scalar i.e. 'a' and hence the expression is well-formed?Bland
Two clarifications: Value computation of assignment operator includes steps (a), (b) and (c). (d) is a side effect of assignment operator.Bland
and the second clarification: If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. - The example discussed above is about side effects being unsequenced with respect to another side effect. Is there an example of 'side effect being unsequenced with respect to value computation using the value of the same scalar object (the second 'or' clause in the statement)Bland
@Bland For the first one, note that value computation alone does not imply side effects or such (see 1.9/12 - it's "Evaluation" that in generally does). It's just that for an assignment expression, its value computation is sequenced after (c), (a) and (b) (the "sequenced before" relation is transitive).Goode
For your second clarification - this was phrased in C++03 using the slender "Furthermore, the prior value shall be accessed only to determine the value to be stored.", which was hardly decisive. C++0x replaces it with what you quoted, and includes orders where necessary. For instance in a++, the previous value of "a" is accessed to determine the value being stored, so C++0x induces an order into this. But for "a + a++", this is not the case with respect to the first "a", so it's not ordered and it becomes undefined. This shows a strong point of the C++0x wording. It's much more reliable.Goode
@Daniel please consider my new answer with the two DR links. They will show that both a = ++a and ++ ++ a is defined behavior and is intended to be so.Goode
@Bland update ^^ (found two DRs that clarify this and support this answer).Goode
@Johannes : Excellent find. You should probably show DR637 to James Kanze who made all of us(excluding you) think that he was right all the time. :-)Whitcomb
Your explanation for ++++v to be well defined is not convincing. C++11 says that: The side effect of the built-in preincrement and predecrement operators is sequenced before its value computation. and that make more sense in this case.Unknowable
@Unknowable I do not understand what you are sayingGoode
I am saying that the reason you explained that ++++v is well defined is seem confusing to me. As per your explanation i = ++i would be well defined (which is not) in C11 too because it says that: An assignment expression has the value of the left operand after the assignment.Unknowable
@JohannesSchaub-litb for "a = b the assignment is sequenced after value computation of b and a",I think no value computation of a(single indentity) here because if it is necessary to invoke value computation of a,then the a=++a would invoke UB in c++11,since the modification in "++a" and the value computation in "a"(the left hand operand) relative to each other are unsequenced,so ,theres' no value computation for single a,Do you agree?Eller
@JohannesSchaub-litb I have seen that answer ,exactly,But for this answer,I hava cavilled to say that this answer would make misunderstand for "value computation" ,if add note for "value computation",it will be perfectlyEller
W
15

That is because in C++ pre-increment operator returns an lvalue and it requires its operand to be an lvalue.

++++++++++phew ; in interpreted as ++(++(++(++(++phew))))

However your code invokes Undefined Behaviour because you are trying to modify the value of phew more than once between two sequence points.

In C, pre-increment operator returns an rvalue and requires its operand to be an lvalue. So your code doesn't compile in C mode.

Whitcomb answered 11/9, 2010 at 7:7 Comment(11)
@Prasoon: Not to second guess you, I am just curious to read about what you said; "you are trying to modify the value of phew more than once between two sequence points". Can you provide an annotation to this part of the standard so I can read more about this?Rms
@Merlyn Morgan-Graham : Read this article by Steve Summit: c-faq.com/expr/seqpoints.html .Whitcomb
Commentary This specification only needs to be followed exactly when there is more than one access between sequence points, or for objects declared with the volatile storage class. As far as the developer is concerned, a modification occurs. As long as the implementation delivers the expected result, it is free to do whatever it likes.Whitcomb
Just to be clear, ++++i is well-defined for user-defined types, right?Skillful
@FredOverflow: Yes because function call introduces a sequence point. :)Whitcomb
I cannot quote the standard, but I believe that the behavior is well defined. The standard requires an specific order of execution. Consider that you had int & f( int & x ) { return ++x; } and the call: int x = 0; f( f( f( x ) ) ); The inner f must be executed before the middle f and that is guaranteed to be performed before the external f call. The semantics if pre-increment require the increment to be performed before the value is yielded. On the other hand, int x = 0; int y = ++x + x++; is undefined as the relative order of execution of the pre and post increment undefined.Billy
@David: §5/4: "Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression." I think Prasoon is right, although it would almost be hard to write a compiler so bad as to get it wrong.Bouchard
@Potatoswatter: I'd guess that the simple mistake you could make in writing the compiler would be to inline the operator++, and then to apply optimisations to the result as if it were a single expression, without somehow annotating the inlined code to retain the sequence point. It's a fairly fundamental error, since if you got it wrong in this case I bet the optimiser would incorrectly coalesce/reorder statements in other cases too, but I think it's an omission rather than a comission...Semivitreous
This is only UB in C++03 though. It's valid in C++0x.Goode
@Johannes: What is changing in C++0x that makes this no longer UB?Elongate
@Daniel see my answer for an explanationGoode

© 2022 - 2024 — McMap. All rights reserved.