4

In C, when there are variables (assume both as int) i less than j, we can use the equation

i^=j^=i^=j

to exchange the value of the two variables. For example, let int i = 3, j = 5; after computed i^=j^=i^=j, I have i = 5, j = 3.

However, if I use two int pointers to re-do this, with *i^=*j^=*i^=*j, using the example above, what I have will be i = 0 and j = 3.


In C

1

    int i=3, j=5;
    i^=j^=i^=j; // after this i = 5, j=3

2

    int i = 3, j= 5;
    int *pi = &i, *pj = &j;
    *pi^=*pj^=*pi^=*pj; // after this, $pi = 0, *pj = 5

In JavaScript

    var i=3, j=5;
    i^=j^=i^=j; // after this, i = 0, j= 3

the result in JavaScript makes this more interesting to me

my sample code , on ubuntu server 11.0 & gcc

    #include <stdio.h>
    int main(){
        int i=7, j=9;
        int *pi=&i, *pj=&j;
        i^=j^=i^=j;
        printf("i=%d j=%d\n", i, j);
        i=7, j=9;
        *pi^=*pj^=*pi^=*pj
        printf("i=%d j=%d\n", *pi, *pj);
    }


undefined behavior in c

Will the undefined behavior in c be the real reason leads to this question?

1

code compiled use visual studio 2005 on windows 7 produce the expected result ( Output i = 7, j = 9 twice.)

2

code compiled use gcc on ubuntu ( gcc test.c ) produce the unexpected result ( Output i = 7, j = 9 then i = 0, j = 9 )

3

code compiled use gcc on ubuntu ( gcc -O test.c ) produce the expected result ( Output i = 7,j = 9 twice. )

Tunaki
  • 132,869
  • 46
  • 340
  • 423
klvoek
  • 81
  • 5
  • 2
    Same idea: http://stackoverflow.com/questions/949433/could-anyone-explain-these-undefined-behaviors-i-i-i-i-i-etc – chris Sep 08 '12 at 14:17
  • Does your code even compile? You are trying to dereference an `int`. – huon Sep 08 '12 at 14:21
  • 3
    Why bother?! `int temp = i; i = j; j = temp;` works every time. – Bo Persson Sep 08 '12 at 17:45
  • Implement that simply is a better way also. But, I just curious about what was happened. – klvoek Sep 09 '12 at 15:08
  • Regarding your edit: undefined behavior certainly can lead to the behavior you're seeing, since undefined behavior means that anything can happen (there is no 'expected' behavior for the compiler). – Michael Burr Sep 09 '12 at 16:00

3 Answers3

8

i^=j^=i^=j is undefined behavior in C.

You are violating sequence points rules by modifying i two times between two sequence points.

It means the implementation is free to assign any value or even make your program crash.

For the same reason, *i^=*j^=*i^=*j is also undefined behavior.

(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."

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

Consider the following code:

 #include <stdio.h>

    int main() {
        int i=7, j=9;
        int *pi=&i, *pj=&j;
        i^=j^=i^=j;
        printf("i=%d j=%d\n", i, j);
        i=7, j=9;
        *pi^=*pj^=*pi^=*pj;
        printf("i=%d j=%d\n", *pi, *pj);

        return 0;
    }

If you try to compile it you will see a warning: unsequenced modification for the first line. As @ouath said it's not well-defined. According to the C11 Standard the assignment-expressions work in a read-modify-write fashion. This operation isn't atomic on all the CPU architectures. You can read more about the warning here.

It's interesting to see that for *pi^=*pj^=*pi^=*pj; there is no warning in my LLVM compiler.

0x90
  • 39,472
  • 36
  • 165
  • 245
  • Hi, This is my first time to learn how to submit a question and eventually encountered some problem with the markdown editor. The question was corrected, look forward for you attention. – klvoek Sep 08 '12 at 14:56
  • 2
    See my answer, `*pi^=*pj^=*pi^=*pj` is undefined behavior in C. You cannot expect any reliable behavior. – ouah Sep 08 '12 at 16:28
  • Which IDE was the c code that you posted on codepad.org compiled on? visual studio c/c++ under Windows Or gcc/g++ under linux ? It seems that code compiled on widows vs2005 c++ produce the right result twice( which expected i = 9, j = 7 ), but gcc on linux seems not. – klvoek Sep 09 '12 at 14:48
  • gcc 4.1.2 flags: -O -fmessage-length=0 -fno-merge-constants -fstrict-aliasing -fstack-protector-all – 0x90 Sep 09 '12 at 16:03
  • 1
    After test, I find that gcc -O let the program compiled produce the expected result. I will wonder what 'gcc -O' had done to the binary code ? And, could you give me some idea about how and why to use gcc -O? – klvoek Sep 10 '12 at 02:37
  • you can use -S to see the assembly, let me know what you are coming up with.... you should compile with and without -O and tkdiff or diff the two assembly files – 0x90 Sep 10 '12 at 03:16
3

As for the 'more interesting' aspect added by the Javascript result:

While the expression is undefined in C as explained in ouah's answer, it is well-defined in Javascript. However the rules for how the expression is evaluated in Javascript might not be what you expect.

The ECMAscript spec says that a compound assignment operator is evaluated like so (ECMA-262 11.13.2):

The production AssignmentExpression : LeftHandSideExpression @= AssignmentExpression,where @ represents one of the operators indicated above, is evaluated as follows:

  1. Evaluate LeftHandSideExpression.
  2. Call GetValue(Result(1)).
  3. Evaluate AssignmentExpression.
  4. Call GetValue(Result(3)).
  5. Apply operator @ to Result(2) and Result(4).
  6. Call PutValue(Result(1), Result(5)).
  7. Return Result(5).

So the expression i ^= j ^= i ^= j would be evaluated in the following steps:

i = (3 ^ (j ^= i ^= j))

i = (3 ^ (j = (5 ^ i ^= j)))

i = (3 ^ (j = (5 ^ (i = 3 ^ j)))))

i = (3 ^ (j = (5 ^ (i = 3 ^ 5)))))

i = (3 ^ (j = (5 ^ (i = 6)))))

i = (3 ^ (j = (5 ^ 6))))

i = (3 ^ (j = 3))   // j is set to 3

i = (3 ^ 3)

i = 0               // i is set to 0

Yet another reason to avoid the trick of using an xor operation to swap values.

Community
  • 1
  • 1
Michael Burr
  • 333,147
  • 50
  • 533
  • 760