void swap(int* a, int* b) {
if (a != b)
*a ^= *b ^= *a ^= *b;
}
作为以上代码*a ^= *b ^= *a ^= *b的快捷方式仅是
*a = *a ^ (*b = *b ^ (*a = *a ^ *b))
,请问第二个*a在被修改(通过等号=)之前是否会被计算(进行异或操作)?是否与程序使用C99/C11/C++98/C++11有关?void swap(int* a, int* b) {
if (a != b)
*a ^= *b ^= *a ^= *b;
}
*a = *a ^ (*b = *b ^ (*a = *a ^ *b))
,请问第二个*a在被修改(通过等号=)之前是否会被计算(进行异或操作)?是否与程序使用C99/C11/C++98/C++11有关?C++11标准规定:
5.17/1: 赋值运算符(=)和复合赋值运算符从右向左结合. (...) 赋值在左右操作数的值计算之后顺序执行,在赋值表达式的值计算之前执行。
1.9/15: 如果对标量对象的副作用与同一标量对象的另一个副作用或使用相同标量对象的值计算无序,那么行为未定义。
因此,*a ^= *b
的顺序如下:
*a
和 *b
。不确定计算顺序*a
中(*a ^= *b)
的结果现在有 *b ^= *a ^= *b
,根据优先级规则是 *b ^= (*a ^= *b)
:
*b
和 (*a ^= *b)
。不确定计算顺序。但由于 (*a ^= *b)
不会修改 *b
,因此无关紧要。*b
中但现在是有未指定的排序 *a ^= *b ^= *a ^= *b
,根据优先级规则为 *a ^= (*b ^= (*a ^= *b))
:
*a
和 (*b ^= (*a ^= *b))
。不确定计算顺序。但由于 (*b ^= (*a ^= *b))
修改了 *a
,所以结果将取决于首先计算哪个值。这显然是一个 U.B. 假设首先评估 *a
(即在任何其他操作之前):
您将获得它的原始值,它将与 (*b ^= (*a ^= *b))
的值异或,即原始的 *b
再次异或上原始的 *a
再次异或上 *b
。这将导致 0(将存储在 *a
中)。
假设首先评估 (*b ^= (*a ^= *b))
,那么它的结果是原始的 *a
,但是 *a
的内容已更改为原始的 *a
异或上原始的 *b
。因此,这将导致原始的 *b
(将存储在 *a
中)
顺便说一句,在两种情况下,*b
包含两个异或 *b
的原始值,这意味着 *b
将包含原始的 *a
。
结论:这里证明了该表达式的最终值唯一确定为 *b
,但是 *a
的最终值未唯一定义(可能有两个值)。因此,它显然是一个未指
void safe_swap(int* a, int* b) {
if (a != b)
*b ^= *a ^= *b, *a ^= *b;
}
在某些没有更多可用内存的嵌入式设备上,在极端情况下可能不得不使用这种高级技巧。但它有缺点。
首先,它很难理解,并且容易出错,正如上面看到的那样。然后,它可能并不像看起来那样高效。一些实现相关的实验显示出不太优化的代码:3个 MOV
和 3 个XOR
,而传统的使用临时变量进行交换只需4个MOV
。一些非正式基准测试表明,它大多数时间可能会慢3到8%。
顺便说一句,传统的交换还可以写成一条语句:
void modern_swap(int*a, int*b) {
if (a!=b)
tie(*a,*b)=make_pair(*b,*a);
}
{int tmp = *a; *a=*b; *b=tmp;}
作为函数体。编译器可以更容易地内联并优化它。 (在我看来,任何关于异或交换操作的讨论都需要指出,在现实生活中不应该使用它,只能作为大家熟悉的语言法律案例。) - Peter Cordestie(*a,*b)=make_pair(*b,*a);
可以完美地完成工作:https://godbolt.org/z/LaaGlh - 优化的结论:永远不要自己做编译器可以为您完成的工作;-) - Christophe
*a
在没有顺序点的情况下被修改了两次)。 - mafso#define SWAP(p, q) (*(p) ^= *(q), *(q) ^= *(p), *(p) = *(q))
对于所有标准都是明确定义的,并且还具有更新后的*p
值(作为问题中的表达式)。是否有任何用例可以使用此功能? - mafso