13

The allegedly "clever" (but actually inefficient) way of swapping two integer variables, instead of using temporary storage, often involves this line:

int a = 10;
int b = 42;

a ^= b ^= a ^= b; /*Here*/

printf("a=%d, b=%d\n", a, b); 

But I'm wondering, compound assignment operators like ^= are not sequence points, are they? Does this mean it's actually undefined behavior?

o11c
  • 15,265
  • 4
  • 50
  • 75
Medinoc
  • 6,577
  • 20
  • 42
  • 2
    If you write code that is difficult to tell what is going on ask yourself if there a more straightforward way that a future developer may understand? – Ed Heal Jul 04 '13 at 16:58
  • 1
    Note that if you've seen this in C++ code, C++ has different rules for the assignment operators that allow certain constructs (I'm not sure about this one) that are undefined in C. –  Jul 04 '13 at 16:58
  • 3
    possible duplicate of [Sequence Point - Xor Swap on Array get wrong result](http://stackoverflow.com/questions/9958514/sequence-point-xor-swap-on-array-get-wrong-result) – Oliver Charlesworth Jul 04 '13 at 17:20
  • @OliCharlesworth I voted there to close because here we have better answers :) – Grijesh Chauhan Jul 04 '13 at 17:37
  • @EdHeal, back in my first years of C, I might have written such code, but I've known better for years now. I put "clever" in quotes for a reason.:-) – Medinoc Jul 05 '13 at 07:12

4 Answers4

18
a ^= b ^= a ^= b; /*Here*/

It is undefined behavior.

You are modifying an object (a) more than once between two sequence points.

(C99, 6.5p2) "Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression.

Simple assignments as well as compound assignments don't introduce a sequence point. Here there is a sequence point before the expression statement expression and after the expression statement.

Sequence points are listed in Annex C (informative) of the c99 and c11 Standard.

ouah
  • 142,963
  • 15
  • 272
  • 331
13

^= are not sequence points, are they

They are not.

Does this mean it's actually undefined behavior?

Yes it is. Don't use this "clever" technique.

nos
  • 223,662
  • 58
  • 417
  • 506
7

There are no sequence points in that expression, so it produces undefined behavior.

You could fix it trivially and retain most of the succinctness by using the comma operator, which does introduce sequence points:

a ^= b, b ^= a, a ^= b;
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • 1
    You may as well put it on three different lines at this point. It will make a nice box, if you care about those things.. – Thomas Jul 04 '13 at 18:36
5

The order of the evaluation of the ^= operators is well defined. What is not well defined is the order in which a and b are modified.

a ^= b ^= a ^= b;

is equivalent to

a ^= (b ^= (a ^= b));

An operator cannot be evaluated before its arguments are evaluated, so it is definitely going to execute a ^= b first.

The reason to have this be undefined behavior is that, to give the compiler more flexibility in doing optimizations, it is allowed to modify the variable values in any order it chooses. It could choose to do this:

int a1 = a ^ b;
int b1 = b ^ a1;
int a2 = a ^ b1;
a = a1;
a = a2;
b = b1;

or this:

int a1 = a ^ b;
int b1 = b ^ a1;
a = a1;
int a2 = a ^ b1;
a = a2;
b = b1;

or even this:

int a1 = a ^ b;
int b1 = b ^ a1;
int a2 = a ^ b1;
a = a2;
a = a1;
b = b1;

If the compiler could only choose one of those three ways to do things, this would just be "unspecified" behavior. However, the standard goes further and makes this be "undefined" behavior, which basically allows the compiler to assume that it can't even happen.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • first explanation is ultimate, but I couldn't understand reason: *`pre-modified or the post-modified values`* – Grijesh Chauhan Jul 04 '13 at 17:09
  • 1
    This is misleading. There is nothing preventing the side effect of an expression from happening after the evaluation has already completed. The result of `a ^= b` is `a ^ b`, and as a side effect, `a` is set to that result. *When* `a` is set to that result is unspecified. In particular, there is nothing that requires it to have finished *before* the outer `a ^= ...` starts. –  Jul 04 '13 at 17:11
  • 4
    The problem with this explanation is that it makes it sound like there are a few possible things that the compiler could choose to do, and the variables might end up with surprising values. But since the behavior is actually undefined, the compiler is allowed to do *anything at all*. A compiler could validly compile `a ^= b ^= a ^= b;` to `printf("XORs are complicated!\n");` and not actually touch `a` or `b`. (More realistically, you could find that other variables in the same cache lines get modified, with bizarre consequences.) – ruakh Jul 04 '13 at 19:37
  • I don't get it. `operator^=` returns a reference to the left operand. The standard says in 5.17 [expr.ass] 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. http://en.cppreference.com/w/cpp/language/eval_order further says "value computation of the assignment expression" means "returning the reference to the *modified* object" – Lingxi Mar 28 '15 at 04:46