使用XOR交换值

这两个宏有什么区别?

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

要么

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

我在这里看到了第二个宏但是不明白为什么它不像第一个那样写? 我错过了一个特殊原因吗?

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

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

C-faq :

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

说明:
第一个是在两个序列点之间修改两次,因此根据语句未定义行为: 在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。 就是这样(不需要考虑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.重点是我的。

第一个在C99中调用未定义的行为有两个原因最明显,因为你不允许在同一序列点内多次修改同一个变量,并且该宏不止一次修改ab ,而第二个使用逗号运营商 :

 #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表达式; 评估后有一个序列点 。[…]

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

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

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

第二个看起来对我很好,因为不含糊:

 b = b ^(a=a^b) 

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