异或交换算法中运算符的未定义行为
Undefined behaviour of operators in XOR swap algorithm?
void swap(int* a, int* b) {
if (a != b)
*a ^= *b ^= *a ^= *b;
}
由于上述*a ^= *b ^= *a ^= *b
只是*a = *a ^ (*b = *b ^ (*a = *a ^ *b))
的快捷方式,是否可以(例如(在修改第 3 个*a
之前(通过 =(评估第 2 *a
(对于 XOR(?
我是否用 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)
。 它没有确定以哪个顺序。但是*b
没有被(*a ^= *b)
修改,所以没关系。 - 执行异或操作
- 赋值完成,即新值存储在
*b
但是现在到根据优先级规则*a ^= *b ^= *a ^= *b
的未指定顺序*a ^= (*b ^= (*a ^= *b) )
:
- 计算
*a
和(*b ^= (*a ^= *b) )
。 它没有确定以哪个顺序。 但随着*a
被(*b ^= (*a ^= *b) )
修改. 因此,结果将取决于首先计算哪个值。这显然是一个U.B。
假设首先评估*a
(即先于其他任何内容(:
你会得到它的原始值,它将与 (*b ^= (*a ^= *b) )
的值一起 xored,即原始*b
与原始 xored *a
再次与 *b
xored 。 这将导致 0(将存储在 *a
中(。
假设先评估(*b ^= (*a ^= *b) )
,那么它的结果是原始*a
,但*a
的内容更改为原始*a
与原始*b
一起。 因此,这将产生原始*b
(将存储在*a
中(
顺便说一下,在这两种情况下,*b
都包含 *a
xored 两次的原始值,*b
意味着*b
将包含原始*a
.
结论:这里证明了*b
的最终值由该表达式唯一确定,但*a
的最终值不是唯一定义的(可能有两个值(。 所以这显然是一个未指定/未定义的结果! 它可能会交换,也可能丢失*a
具体取决于您的编译器。
如何确定交换?
我在上面已经演示了前两个复合赋值是明确指定的。因此,我们只需要确保最后一个复合赋值是在它之后完成的。 这可以通过逗号运算符来保证:
5.18/1:从左到右计算一对用逗号分隔的表达式,并丢弃左表达式的值
因此,以下方法将起作用:
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);
}
- 体系结构x86_64的未定义符号:std:terminate(),typeinfo,运算符delete[],运算符new
- 未定义的引用和运算符 << vs me
- C++:对函子重载调用运算符的未定义引用
- 对模板运算符的未定义引用,其定义位于同一头文件中
- 在 tensorflow-GPU 中使用用户运算符时未定义的符号>=1.15
- 为什么T是未定义的?我正在尝试实现一个用于双链表的节点类,它不喜欢我使用友元运算符后的T
- 调用提取重加载器会产生对"运算符"的未定义引用错误>>
- 创建指针是否超过非数组指针的末尾,而不是从 C++17 中的一元运算符和未定义的行为派生?
- 编译Qt项目给出了对运算符delete(void*,unsigned int)的未定义引用
- 在 C 和 C++ 中,使用逗号运算符的表达式是否未定义"a = b, ++a;"?
- 未定义对运算符的引用!= 使用 std::unordered_set 时
- C2676 - 二进制'++'未定义此运算符
- 未定义 Lambda 复制分配运算符
- 为什么要重载以前未定义的运算符
- std::可选运算符*() 和运算符>() - 未定义的行为
- 对运算符>>的未定义引用会导致LINK2019
- 重复使用前缀 ++ 运算符时的未定义行为
- 当c 中的矢量容器过载[]运算符时,我对未定义的索引返回什么
- std::映射运算符[]--未定义的行为
- C2676 二进制"++":设置迭代器<TElement>未定义此运算符