在C中,当存在变量(假设int
)i
小于j
时,我们可以使用等式
i^=j^=i^=j
交换两个变量的值。例如,让int i = 3
,j = 5
;在计算i^=j^=i^=j
之后,我有i = 5
,j = 3
。
但是,如果我使用两个int指针重新执行此操作,使用*i^=*j^=*i^=*j
,使用上面的示例,我将拥有i = 0
和j = 3
。
<小时/> 在C
int i=3, j=5;
i^=j^=i^=j; // after this i = 5, j=3
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中未定义的行为是导致这个问题的真正原因吗?
代码编译使用 Visual Studio 2005在Windows 7上 产生预期结果(输出i = 7,j = 9两次。)
代码编译使用 gcc on ubuntu ( gcc test.c )产生意外结果(输出i = 7,j = 9然后i = 0,j = 9)
代码编译使用 gcc on ubuntu ( gcc -O test.c )产生预期结果(输出i = 7,j = 9两次。)
答案 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,其中@
代表其中一个运算符 如上所述,评估如下:
- 评估LeftHandSideExpression。
- 调用GetValue(Result(1))。
- 评估AssignmentExpression。
- 调用GetValue(Result(3))。
- 将operator @应用于Result(2)和Result(4)。
- 调用PutValue(结果(1),结果(5))。
- 返回结果(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操作交换值的技巧的另一个原因。