使用XOR交换值

时间:2013-12-30 15:37:56

标签: c bit-manipulation swap undefined-behavior sequence-points

这两个宏之间有什么区别?

#define swap(a, b)    (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b)))

或者

#define swap(a, b)    (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))

我看到了第二个宏here但是无法理解为什么它不像第一个那样写?我错过了一个特殊原因吗?

3 个答案:

答案 0 :(得分:4)

首先在C99和C11中调用未定义的行为

在C99中,可以理解为;他们将调用未定义的行为,因为缺少序列点

C-faq

  

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。此外,只能访问先前值以确定要存储的值。

<强>解释
第一个是在两个序列点之间修改a两次,因此根据语句未定义行为:在上一个和下一个序列点之间,对象的存储值最多只能通过评估一次来修改一次。表达。就是这样(不需要考虑b)。

C11文件说:

6.5表达式(p2):

  

如果对标量对象的副作用相对于无效,对同一标量对象的不同副作用或使用相同标量值的值计算对象,行为未定义。如果表达式的子表达式有多个允许的排序,则如果在任何排序中发生这种未测序的副作用,则行为是不确定的。 84)

(a) ^= (b) ^= (a) ^= (b)中,a的副作用未被排序,因此会调用未定义的行为。值得注意的是,C11 6.5 p1表示:

  

[...]运算符的操作数的值计算在运算符的结果的值计算之前被排序。

这保证了

(a) ^= (b) ^= (a) ^= (b)  
 |      |      |      | 
 1      2      3      4  

保证在最左边^=运算符的结果计算之前计算所有子表达式1,2,3和4。但是,这并不能保证在最左边^=运算符的结果的值计算之前保证表达式3的副作用。


<子> 1。重点是我的。

答案 1 :(得分:2)

第一个在C99中调用undefined behavior有两个原因最明显,因为不允许在同一个sequence point内多次修改同一个变量,并且该宏修改了a }和b不止一次,而第二个使用comma operator

#define swap(a, b)    (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))
                                                        ^

引入了一个序列点,但没有删除C99中的所有未定义行为,因为正在读取b的先前值以计算a的值,但只能用于确定值的值存储到b

C99标准草案部分6.5 表达式段落 2 的相关部分说明了(强调我的前进):

  

在上一个和下一个序列点之间,对象应具有其存储的值   通过表达式的评估最多修改一次 72)此外,先前值应该只读以确定要存储的值 73)

以及逗号运算符,来自6.5.17 逗号运算符段落 2

  

逗号运算符的左操作数被计算为void表达式; 有一个   评估后的序列点。[...]

答案 2 :(得分:1)

为了更好地理解为什么第一个未定义,这是提供它的另一种方式:
这是因为在C中,你无法控制子表达式之间的执行顺序:

a = a^(b=b^(a=a^b))

对于在=之后发生的第一个,C编译器可以选择使用a的初始值或a的修改值。因此,它显然是模棱两可的,并导致未定义的行为。

第二个对我来说没问题,因为不含糊:

b = b ^(a=a^b)

a和b出现在表达式(a^b)&&...的第一部分这一事实对我来说似乎不是问题,因为&amp;&amp;强制首先评估第一部分。但是,我更愿意让专家分析标准,我不是专家......