表达式中有序列点a ^ = b ^ = a ^ = b,还是未定义?

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(资料性附录)中.


nos*_*nos 13

^ =不是序列点,是它们

他们不是.

这是否意味着它实际上是未定义的行为?

是的.不要使用这种"聪明"的技术.


jam*_*lin 7

该表达式中没有序列点,因此会产生未定义的行为.

您可以通过使用逗号运算符来简单地修复它并保留大部分简洁性,这引入序列点:

a ^= b, b ^= a, a ^= b;
Run Code Online (Sandbox Code Playgroud)


Vau*_*ato 5

^=操作员的评估顺序是明确的.没有明确定义的是订单ab修改的顺序.

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)

如果编译器只能选择这三种方法中的一种来做事,那么这只是"未指定"的行为.然而,标准更进一步,使这是"不确定"的行为,这基本上允许编译器假定它甚至不能发生.

  • 这种解释的问题在于它听起来像编译器可能选择做的一些可能的事情,并且变量可能最终会出现令人惊讶的值.但由于行为实际上是未定义的,因此允许编译器执行*任何内容*.编译器可以有效地编译`a ^ = b ^ = a ^ = b;`到`printf("XORs很复杂!\n");`而不是实际触及`a`或`b`.(更现实地,您可能会发现同一缓存行中的其他变量会被修改,产生奇怪的后果.) (4认同)