4

在 C 中,当有变量(假设两者都为inti小于j时,我们可以使用等式

i^=j^=i^=j

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

但是,如果我使用两个 int 指针重新执行此操作,*i^=*j^=*i^=*j使用上面的示例,我所拥有的将是i = 0and j = 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 和 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

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

2

在 ubuntu ( gcc test.c ) 上使用 gcc 编译的代码会产生意想不到的结果 ( Output i = 7, j = 9 then i = 0, j = 9 )

3

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

4

3 回答 3

8

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

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

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

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

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

于 2012-09-08T16:25:13.580 回答
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 所说,它的定义并不明确。根据C11 标准,赋值表达式以读-修改-写方式工作。此操作在所有 CPU 架构上都不是原子操作。您可以在此处阅读有关警告的更多信息。

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

于 2012-09-08T14:18:45.337 回答
3

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

虽然表达式在 C 中未定义,如 ouah 的回答中所述,但它在 Javascript 中定义良好。但是,在 Javascript 中如何计算表达式的规则可能不是您所期望的。

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

产生式 AssignmentExpression : LeftHandSideExpression @= AssignmentExpression,其中@表示上述运算符之一,计算如下:

  1. 评估 LeftHandSideExpression。
  2. 调用 GetValue(Result(1))。
  3. 评估 AssignmentExpression。
  4. 调用 GetValue(Result(3))。
  5. 将运算符 @ 应用于 Result(2) 和 Result(4)。
  6. 调用 PutValue(Result(1), Result(5))。
  7. 返回结果(5)。

因此,表达式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 操作来交换值的技巧的另一个原因。

于 2012-09-08T20:23:00.237 回答