C++ 异或交换算法中算子的未定义行为?

C++ 异或交换算法中算子的未定义行为?,c++,c,operators,swap,undefined-behavior,C++,C,Operators,Swap,Undefined Behavior,由于上面的*a^=*b^=*a^=*b只是*a^(*b=*b^(*a=*a^*b))的一个快捷方式,所以(例如)可以(例如)在第三个*a修改之前(由=)对第二个*a进行评估(针对XOR) 我是否用C99/C11/C++98/C++11编写它重要吗?C++11标准说: 5.17/1:赋值运算符(=)和复合赋值运算符都从右向左分组。(……)任务是 在右操作数和左操作数的值计算后排序, 在赋值表达式的值计算之前 1.9/15:如果标量对象上的副作用相对于同一标量对象或值上的另一副作用未排序 使用相同标

由于上面的
*a^=*b^=*a^=*b
只是
*a^(*b=*b^(*a=*a^*b))
的一个快捷方式,所以(例如)可以(例如)在第三个
*a
修改之前(由=)对第二个
*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))
    的值异或,即原始
    *b
    与原始
    *a
    异或再次与
    *b
    异或。这将导致0(存储在
    *a
    中)

    假设首先计算
    (*b^=(*a^=*b))
    ,然后其结果为原始
    *a
    ,但
    *a
    的内容更改为原始
    *a
    与原始
    *b
    异或。因此,这将导致原始的
    *b
    (将存储在
    *a
    中)

    顺便说一下,在这两种情况下,
    *b
    包含
    *a
    的原始值,与
    *b
    异或两次,意味着
    *b
    将包含原始
    *a

    结论:这里证明了
    *b
    的最终值是由该表达式唯一确定的,但是
    *a
    的最终值不是唯一定义的(可能有两个值)。因此,这显然是一个未指定/未定义的结果!根据编译器的不同,它可能会交换或丢失
    *a

    如何确保交换? 我已经在上面演示了前两个复合赋值是很好指定的。 所以我们只需要确保最后一个复合赋值是在它之后完成的。这可以通过逗号运算符来保证:

    5.18/1:从左到右计算由逗号分隔的一对表达式,并丢弃左表达式的值

    因此,以下措施将起作用:

    void swap(int* a, int* b) {
        if (a != b)
            *a ^= *b ^= *a ^= *b;
    }
    
    编辑:但为什么要进行异或交换? 在一些没有更多可用内存的嵌入式设备上,在极端条件下,人们可能不得不使用这种高级技巧。但它也有缺点

    首先,它很难理解,如上所述,容易出错。那么它可能没有看上去那么有效。一些依赖于实现的实验:3个
    MOV
    和3个
    XOR
    ,而使用临时变量的经典交换只有4个
    MOV
    。有人建议,在大多数情况下,速度可能会慢3%到8%

    顺便说一下,经典掉期也可以写在一条语句中:

    void safe_swap(int* a, int* b) {
        if (a != b)
            *b ^= *a ^= *b, *a ^= *b;
    }
    

    我记得这里有一次讨论,关于新的排序规则在C11中是否允许这样做。在C99中,它显然是未定义的(<代码> *A < /COD>两次之间没有序列点)。我记得C++对其赋值操作符做了额外的排序保证,因为C赋值操作符返回值,但是C++赋值操作符返回LValk,随后的左值到右值的转换应该具有定义良好的行为。结果可能是C++中的有效,但我不确定。@ HV:C11采用线程排序标准,采用C++排序模型。赋值的LHS的修改现在在对LHS和RHS求值后排序。我唯一使用XOR hack的是一个宏(因为我不需要知道声明临时的类型,并且可以对所有整数类型使用相同的
    SWAP
    宏。如果这应该扩展到一个表达式,
    \define SWAP(p,q)(*(p)^=*(q),*(q)^=*(p),*(p)=*(q))
    是为所有标准定义的,并且具有更新的
    *p
    值(如问题中的表达式).是否有任何使用案例?@mafso;在C11中,分配的LHS修改确实是在LHS和RHS评估后排序的,但并不保证对RHS的修改是正确的
    void modern_swap(int*a, int*b) {
        if (a!=b) 
            tie(*a,*b)=make_pair(*b,*a);
    }