这两个宏之间有什么区别?
#define swap(a, b) (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b)))
或者
#define swap(a, b) (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))
我看到了第二个宏here但是无法理解为什么它不像第一个那样写?我错过了一个特殊原因吗?
答案 0 :(得分:4)
首先在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。重点是我的。
答案 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;强制首先评估第一部分。但是,我更愿意让专家分析标准,我不是专家......