异或交换算法中运算符的未定义行为


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排序如下:

  1. 计算*a*b不确定在哪个次序
  2. 执行异或操作
  3. 赋值完成,即新值存储在*a
  4. 新值用作表达式的结果(*a ^= *b)

现在*b ^= *a ^= *b,根据优先级规则*b ^= (*a ^= *b)

  1. 计算*b(*a ^= *b)。 它没有确定以哪个顺序。但是*b没有被(*a ^= *b)修改,所以没关系。
  2. 执行异或操作
  3. 赋值完成,即新值存储在*b

但是现在到根据优先级规则*a ^= *b ^= *a ^= *b未指定顺序*a ^= (*b ^= (*a ^= *b) )

  1. 计算*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);
} 

最新更新