为什么i ^ = j ^ = i ^ = j不等于* i ^ = * j ^ = * i ^ = * j

时间:2012-09-08 14:16:15

标签: javascript c pointers logical-operators

在C中,当存在变量(假设inti小于j时,我们可以使用等式

i^=j^=i^=j

交换两个变量的值。例如,让int i = 3j = 5;在计算i^=j^=i^=j之后,我有i = 5j = 3

但是,如果我使用两个int指针重新执行此操作,使用*i^=*j^=*i^=*j,使用上面的示例,我将拥有i = 0j = 3

<小时/> 在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

在JavaScript中

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

JavaScript中的结果让我对此更感兴趣

我的示例代码,在ubuntu服务器11.0&amp; 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);
    }

<小时/>

c

中的未定义行为

c中未定义的行为是导致这个问题的真正原因吗?

1

代码编译使用 Visual Studio 2005在Windows 7上 产生预期结果(输出i = 7,j = 9两次。)

2

代码编译使用 gcc on ubuntu gcc test.c )产生意外结果(输出i = 7,j = 9然后i = 0,j = 9)

3

代码编译使用 gcc on ubuntu gcc -O test.c )产生预期结果(输出i = 7,j = 9两次。)

3 个答案:

答案 0 :(得分:8)

i^=j^=i^=j是C中未定义的行为。

您通过在两个序列点之间修改i两次来违反序列点规则。

这意味着实现可以自由分配任何值,甚至可以使程序崩溃。

出于同样的原因,*i^=*j^=*i^=*j也是未定义的行为。

  

(C99,6.5p2)“在上一个和下一个序列点之间,对象的存储值最多只能通过表达式的一次修改一次。”

答案 1 :(得分:4)

请考虑以下代码:

 #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;
    }

如果您尝试编译它,您会看到第一行warning: unsequenced modification。正如@ouath所说,它没有明确定义。根据{{​​3}},赋值表达式以C11 Standard方式工作。此操作在所有CPU体系结构上都不是原子操作。您可以阅读有关警告read-modify-write的更多信息。

有趣的是,对于*pi^=*pj^=*pi^=*pj;,我的LLVM编译器中没有警告。

答案 2 :(得分:3)

至于Javascript结果添加的“更有趣”方面:

虽然表达式在C as explained in ouah's answer中未定义,但它在Javascript中定义良好。但是,如何在Javascript中评估表达式的规则可能不是您所期望的。

ECMAscript规范说复合赋值运算符的评估方式如下(ECMA-262 11.13.2):

  

制作AssignmentExpression:LeftHandSideExpression @=   AssignmentExpression,其中@代表其中一个运算符   如上所述,评估如下:

     
      
  1. 评估LeftHandSideExpression。
  2.   
  3. 调用GetValue(Result(1))。
  4.   
  5. 评估AssignmentExpression。
  6.   
  7. 调用GetValue(Result(3))。
  8.   
  9. 将operator @应用于Result(2)和Result(4)。
  10.   
  11. 调用PutValue(结果(1),结果(5))。
  12.   
  13. 返回结果(5)。
  14.   

因此,将在以下步骤中评估表达式i ^= j ^= i ^= j

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

避免使用xor操作交换值的技巧的另一个原因。