Med*_*noc 13 c undefined-behavior sequence-points compound-assignment
据称"巧妙"(但实际上效率低下)交换两个整数变量而不是使用临时存储的方式通常涉及这一行:
int a = 10;
int b = 42;
a ^= b ^= a ^= b; /*Here*/
printf("a=%d, b=%d\n", a, b);
Run Code Online (Sandbox Code Playgroud)
但我想知道,复合赋值运算符^=
不是序列点,是吗?这是否意味着它实际上是未定义的行为?
oua*_*uah 18
a ^= b ^= a ^= b; /*Here*/
Run Code Online (Sandbox Code Playgroud)
这是未定义的行为.
您正在a
两个序列点之间多次修改对象().
(C99,6.5p2)"在上一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次.
简单赋值和复合赋值不会引入序列点.这里在表达式语句表达式之前和表达式语句之后有一个序列点.
序列点列于c99和c11标准的附录C(资料性附录)中.
该表达式中没有序列点,因此会产生未定义的行为.
您可以通过使用逗号运算符来简单地修复它并保留大部分简洁性,这会引入序列点:
a ^= b, b ^= a, a ^= b;
Run Code Online (Sandbox Code Playgroud)
^=
操作员的评估顺序是明确的.没有明确定义的是订单a
和b
修改的顺序.
a ^= b ^= a ^= b;
Run Code Online (Sandbox Code Playgroud)
相当于
a ^= (b ^= (a ^= b));
Run Code Online (Sandbox Code Playgroud)
在评估参数之前无法对运算符求值,因此它肯定会先执行a ^= b
.
使其成为未定义行为的原因是,为了使编译器在进行优化时具有更大的灵活性,允许以其选择的任何顺序修改变量值.它可以选择这样做:
int a1 = a ^ b;
int b1 = b ^ a1;
int a2 = a ^ b1;
a = a1;
a = a2;
b = b1;
Run Code Online (Sandbox Code Playgroud)
或这个:
int a1 = a ^ b;
int b1 = b ^ a1;
a = a1;
int a2 = a ^ b1;
a = a2;
b = b1;
Run Code Online (Sandbox Code Playgroud)
甚至这个:
int a1 = a ^ b;
int b1 = b ^ a1;
int a2 = a ^ b1;
a = a2;
a = a1;
b = b1;
Run Code Online (Sandbox Code Playgroud)
如果编译器只能选择这三种方法中的一种来做事,那么这只是"未指定"的行为.然而,标准更进一步,使这是"不确定"的行为,这基本上允许编译器假定它甚至不能发生.