6

Does the following code invoke undefined behavior in C?

int a = 1, b = 2;
a = b = (a + 1);

I know that the following does invoke UB:

a = b = a++;

The reason is that it violates the following clause from the standard:

Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.

However, the first snippet does not violate this clause. A coworker says that statement a = b = a+1 can mean either

a = a + 1;
b = a + 1;

or

b = a + 1;
a = b;

I think that due to the "right-to-left" associativity of =, it always must mean a = (b = (a+1)), not

a = a + 1;
b = a + 1;

I'm not positive, though. Is it UB?

P.P
  • 117,907
  • 20
  • 175
  • 238
anjruu
  • 1,224
  • 10
  • 24
  • You are correct about the associativity laws. The result of the expression `b = (a+1)` is the value that `b` would take after the assignment (although the assignment is not sequenced before that evaluation, prior to C11 anyway, but that doesn't matter here). – M.M May 18 '15 at 13:58
  • The value of an assignment expression is that of the left operand (after assignment). So, your co-worker is wrong - it can only mean the second. – Sander De Dycker May 18 '15 at 13:59
  • 2
    That duplicate had no relevance to the question. However, this might be a duplicate: http://stackoverflow.com/questions/19353686/multiple-assignment-in-one-line – Lundin May 18 '15 at 14:05
  • @gopi With all due respect, that is not a duplicate. Apparently, OP knows that `a = a++` is UB, he wanted to know whether `a = b = (a + 1);` is also UB or not. – Sourav Ghosh May 18 '15 at 14:05
  • 1
    Note that `a = a = a +1` is not well-defined however. Because then there are two side effects on the same variable in the same expression, without a sequence point in between. And of course, if you don't use this _dangerous, completely stupid and completely superfluous_ feature called "multiple assignment" at all, there will be no problems nor any confusion. – Lundin May 18 '15 at 14:39
  • @Lundin: In cases where it is necessary to store the result of a function to multiple "complicated" lvalues, saying something like `foo[r][c] = moo[r][c] = someFunction()`; can be cleaner than creating a temporary value especially for the purpose. It would be nicer still if there were a means for code to compile-time assert that it is only suitable for use on compilers where redundant assignment to the same lvalue will not cause nasal demons [a property possessed by the vast majority of compilers]. – supercat Jul 25 '15 at 17:15

4 Answers4

12

IMHO, a = b = a+1 is well-defined.

Here. you're not changing the value of a, just using it, in case of a+1.

To be explicit, according to the "right-to-left" associativity of = operator, you can break down the above as,

b = a + 1;  //a does not change here, only the value is used.
a = b;
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • 2
    The smart way would of course be to write the code just as above, rather than using multiple assignment on one single row. But if all C programmers (including Dennis Ritchie) were able to use common sense, we wouldn't have all these interesting discussions about operator precedence and undefined behavior, and then the traffic on SO would be severely reduced! :) – Lundin May 18 '15 at 14:44
4

Here is C99's full list of sequence points (adapted from C99, Annex C):

  • during a function call, after the arguments have been evaluated and before any part of the function body is executed;
  • at the end of the first operand of the following operators: &&, ||, ?:, ,;
  • at the end of a full declarator;
  • at he end of a full expression (an initializer, the expression in an expression statement, the expression in a return statement, each of the expressions in a for statement, or the controlling expression of an if, switch, while, or do statement);
  • immediately before a library function returns;
  • after the actions associated with each formatted input/output function conversion specifier;
  • immediately before and immediately after each call to a comparison function; and also between any call to a comparison function and any movement of the objects passed as arguments to that call (refers to bsearch() and qsort().

Considering your code in that light:

int a = 1, b = 2;
a = b = (a + 1);

there are sequence points

  1. after a = 1 (full declarator)
  2. after b = 2 (full declarator)
  3. at the end of the second statement. (full expression)

The whole of a = b = (a + 1) is a single expression statement, and it contains no internal sequence points. However, it does not violate the prohibition against an object having its value modified more than once between sequence points: a and b are each modified exactly once in the whole statement.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Perhaps more relevant to the question, 6.5.16 (assignment operator): `The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.` Meaning we can't know if `b` is evaluated before `a+1` (which doesn't matter, unless the variables are volatile). – Lundin May 18 '15 at 14:46
  • @Lundin, you must be looking at C11, whereas my answer is explicitly given in C99 context. The corresponding C99 text is "The side effect of updating the stored value of the left operand shall occur between the previous and the next sequence point. The order of evaluation of the operands is unspecified." – John Bollinger May 18 '15 at 15:09
3

a = b = a + 1; is the same as :

b = a + 1;
a = b;

You do not change the value of the left hand side in the right hand sides, so it is well defined.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • If `b` is volatile, the statements are not equivalent; the latter formulation would require that the compiler read `b` after writing it; the former may or may not, depending upon the compiler [some compilers guarantee that the value of an assignment is the type-coerced stored value, rather than what the variable holds afterword; code which is targeted for such compilers may avoid the need for an otherwise-unnecessary temp. – supercat Jul 25 '15 at 17:23
2
a = a++;

is different compared to

a = a+1;

In the second case you are not modifying the value of a by doing a+1 whereas in the first case the value of a is being changed which will lead to undefined behavior.

Gopi
  • 19,784
  • 4
  • 24
  • 36