93

I've got the following code:

#include <stdio.h>
int main(int argc, char **argv) {
    int i = 0;
    (i+=10)+=10;
    printf("i = %d\n", i);
    return 0;
}

If I try to compile it as a C source using gcc I get an error:

error: lvalue required as left operand of assignment

But if I compile it as a C++ source using g++ I get no error and when i run the executable:

i = 20

Why the different behavior?

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
Svetlin Mladenov
  • 4,307
  • 24
  • 31
  • 85
    Different language, different syntax rules?. Personally, I would reject that code in code-review. – Max May 18 '12 at 13:53
  • 7
    Avoid code like this imo... Unclear for everyone. – allaire May 18 '12 at 13:54
  • 1
    Undoubtfully, the code isn't clean and should be avoid in "real" development. But nevertheless, I observe the same behavior and would like to know reasons for it. – ulidtko May 18 '12 at 13:56
  • 9
    This is NOT a code excerpt from a real piece of software. This is just a kink I've stumbled upon accidentally. – Svetlin Mladenov May 18 '12 at 13:58
  • 3
    @JohnDibling I think the upvote is specifically the (i += 10) += 10 i don't know what language that is legitimate code in and the fact that he says C++ actually compiles it intrigues me. – Tony318 May 18 '12 at 19:18
  • @DonaldDuck "behaviour" is also correct spelling. Seems like an unnecessary edit – default Feb 11 '17 at 13:53
  • 1
    @Default I edited mostly because "C++" and "g++" shouldn't be formatted as code. But my spell checker said that "behaviour" was wrong, so I corrected it as the spell checker suggested. – Donald Duck Feb 11 '17 at 13:56

2 Answers2

134

Semantics of the compound assignment operators is different in C and C++:

C99 standard, 6.5.16, part 3:

An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment, but is not an lvalue.

In C++ 5.17.1:

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue with the type and value of the left operand after the assignment has taken place.

EDIT : The behavior of (i+=10)+=10 in C++ is undefined in C++98, but well defined in C++11. See this answer to the question by NPE for the relevant portions of the standards.

Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Correct. One returns the result value, and one returns the variable (address) – SwiftMango May 18 '12 at 14:00
  • 7
    **Important**: Note that `(i+=10)+=10` is undefined behavior in C++, see @aix answer. – David Rodríguez - dribeas May 18 '12 at 15:05
  • @DavidRodríguez-dribeas You meant *unspecified*, not *undefined*, right? – Sergey Kalinichenko May 18 '12 at 15:08
  • 4
    @dasblinkenlight: No, he meant *undefined*. In C++03 and earlier, modifying the lvalue result of an expression **behaves unpredictably in all compilers** due to the lack of intervening sequence point. If it were *unspecified*, it would **behave predictably but differently on different compilers**. – Justin ᚅᚔᚈᚄᚒᚔ May 18 '12 at 15:50
  • @Justinᚅᚔᚈᚄᚒᚔ It looks like this is dependent on the standard: in C++11 it is well-defined. See [this question](http://stackoverflow.com/q/10655290/335858) for more info. – Sergey Kalinichenko May 18 '12 at 16:28
  • @Giorgio This has been undefined in C++98 and C++03, but it is fully specified in C++11. See [this answer](http://stackoverflow.com/a/10655884/335858) for more details. – Sergey Kalinichenko May 18 '12 at 16:32
  • @dasblinkenlight: Thanks for the link, but then what _was_ it good for from 1998 to 2011? – Giorgio May 18 '12 at 16:33
  • @Giorgio Not much: arguably, it is a "hole" in the standard that has been patched in 2011. – Sergey Kalinichenko May 18 '12 at 16:36
  • @dasblinkenlight: Are you sure that the quote from the standard guarantees that the expression is well defined? I am not. Note that the standard talks about *value computations* and *side effects*, and the ordering is only on *value computations*. – David Rodríguez - dribeas May 18 '12 at 17:37
  • @DavidRodríguez-dribeas aix has posted a related question [here](http://stackoverflow.com/q/10655290/335858), with answers indicating that the standard has moved this expression from the category of undefined ones to that of well-defined as recently as in its C++11 release. The expression remained undefined prior to that, I think unintentionally: while most compilers did the expected reasonable thing, the standard failed to spell out the rules that apply to such expressions. – Sergey Kalinichenko May 18 '12 at 17:48
  • @dasblinkenlight: I was attempting to correct your "unspecified vs undefined" statement. I am aware that C++11 has clarified the behavior of this construct, which is why my response said *"In C++03 and earlier"*. The fact still remains: **prior to C++11**, this expression results in *undefined* behavior. – Justin ᚅᚔᚈᚄᚒᚔ May 18 '12 at 18:09
  • @Justinᚅᚔᚈᚄᚒᚔ You are correct on that one: after reading through the relevant parts of the standard, I realized that it was indeed undefined, not unspecified, behavior. – Sergey Kalinichenko May 18 '12 at 18:12
  • 2
    It would have been useful in a setting like `int f(int &y); f(x += 10);` - passing a reference to the modified variable into a function. – Phil Miller May 18 '12 at 21:00
  • Note that the behavior IS well defined in earlier standards IF `i` is a class type with a user-defined `operator+=` function. – Chris Dodd May 21 '12 at 17:10
  • @akappa: I still don't understand why & how (i+=10)+=10; is undefined behavior in C++98 & C++03? Will you please explain it? – Destructor Oct 23 '15 at 14:35
51

In addition to being invalid C code, the line

(i+=10)+=10;

would result in undefined behaviour in both C and C++03 because it would modify i twice between sequence points.

As to why it's allowed to compile in C++:

[C++N3242 5.17.1] The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand.

The same paragraph goes on to say that

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

This suggests that in C++11, the expression no longer has undefined behaviour.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • 3
    Well, it’s certainly UB because of the sequence points. It’s also *invalid code* in C (but not C++) but that’s unrelated to sequence points, and should be caught by the compiler. – Konrad Rudolph May 18 '12 at 13:57
  • 2
    @KonradRudolph: no, the compiler is not obliged to catch undefined behavior, as opposed to ill-formed code. The "should be caught by the compiler" part is where we disagree. –  May 18 '12 at 14:03
  • @Fanael Ah, sorry for the confusion. Yes, I edited the comment to include “but not in C++” since I noticed that it might be misleading. I’ll delete my other comments now, they serve nothing. – Konrad Rudolph May 18 '12 at 14:09
  • 2
    Sequence points don't exist in C++11, so the actual reason for the UB is that there are two modifications to `i` that are unsequenced. – Mankarse May 18 '12 at 14:11
  • 4
    It's false that this is undefined behavior. If the assignment weren't sequence before the value computation of the assignment expression then `i = j+=1` would result in an indeterminate value. From the same paragraph you quote "In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression." Therefore `(i+=10)+=10` is well defined to do `i += 10; i += 10;`. On the other hand `(i+=10)+=(i+=10)` is UB. – bames53 May 18 '12 at 14:39
  • 1
    @Mankarse The two modifications _are_ sequenced. **"In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression."** – bames53 May 18 '12 at 15:10
  • @bames53: It's an interesting point that you make, but I'm not sure that it is correct. The value computation may be sequenced before the assignment, but the side effects are not. – Mankarse May 18 '12 at 15:11
  • 1
    @Barnes Oh wait... you are entirely correct. Yes, I just realised (: – Mankarse May 18 '12 at 15:15
  • 2
    Since I saw that there was some disagreement on this between the commenters, I've posted this as a separate question: http://stackoverflow.com/questions/10655290/in-c-does-i-10-10-result-in-undefined-behaviour – NPE May 18 '12 at 15:20
  • 1
    Interestingly, although C11 introduced sequenced-before/sequenced-after/unsequenced language, the assignment is described with **The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands.** (in n1570) It misses the second part of the C++ sentence. – Cubbi May 18 '12 at 15:52
  • In C, the code violates a constraint, requiring a diagnostic. But it is also undefined behavior. C implementations are not required to reject programs that trigger mandatory diagnostics. If such a program is compiled anyway and executed, its behavior is not defined by the C standard. So there is no conflict between "breaks syntax rule or violates a constraint" and "has undefined behavior". If the program is rejected, then of course there is no opportunity for undefined behavior to take place. – Kaz May 18 '12 at 17:03
  • 1
    There are common situations in which nonstandard code that requires a diagnostic is compiled anyway. For instance, the GNU C compiler only emits a warning for some pointer conversions that require a cast. That is a required ISO C diagnostic. The programs are compiled anyway, and so they are not defined ISO C, though they may be well-defined GNU C. – Kaz May 18 '12 at 17:04
  • Note that the behavior IS well defined IF `i` is a class type with a user-defined `operator+=` function. That's because the function introduces implicit sequence points on entry and exit. There's no side effects involved outside of the user-define `operator+=` – Chris Dodd May 21 '12 at 17:09
  • Could you guys possibly take it to chat, hash it out, and then add a link to the bookmark of the conversation here? Thanks in advance. – casperOne May 21 '12 at 17:10