这两个宏有什么区别?
#define swap(a, b) (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b)))
或者
#define swap(a, b) (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))
我在这里看到了第二个宏,但不明白为什么它不像第一个那样写?我错过了有什么特殊原因吗?
这两个宏有什么区别?
#define swap(a, b) (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b)))
或者
#define swap(a, b) (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))
我在这里看到了第二个宏,但不明白为什么它不像第一个那样写?我错过了有什么特殊原因吗?
First 将在 C99 和 C11 中调用未定义的行为。
在C99中,可以理解为;由于缺少序列点,它们将调用未定义的行为。
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。此外,只能访问先验值以确定要存储的值。
说明:
第一个a
在两个序列点之间修改两次,因此根据语句未定义行为:在前一个序列点和下一个序列点之间,对象应通过表达式的评估最多修改一次其存储的值。就是这样(无需考虑b
)。
C11 文档说:
如果标量对象的副作用相对于同一标量对象的不同副作用或使用同一标量对象的值的值计算是未排序的,则行为未定义。如果一个表达式的子表达式有多个允许的排序,则如果在任何排序中出现这种未排序的副作用,则行为是未定义的。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 中调用未定义的行为有两个最明显的原因,因为您不允许在同一个序列点内多次修改同一个变量,并且该宏修改了这两个a
并且b
不止一次,而第二个使用逗号运算符:
#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))
对于 = 之后出现的第一个 a,C 编译器可以选择使用 a 的初始值或 a 的修改值。因此,它显然是模棱两可的,并导致未定义的行为。
第二个对我来说看起来不错,因为没有歧义:
b = b ^(a=a^b)
a 和 b 出现在表达式的第一部分这一事实(a^b)&&...
对我来说似乎不是问题,因为 && 强制首先评估第一部分。但是,我更愿意让专家剖析标准,我不是专家......