C 用异或交换值
这两个宏之间有什么区别C 用异或交换值,c,bit-manipulation,swap,undefined-behavior,sequence-points,C,Bit Manipulation,Swap,Undefined Behavior,Sequence Points,这两个宏之间有什么区别 #define swap(a, b) (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b))) 或 我看到了第二个宏,但不明白为什么它写得不像第一个宏?我错过了什么特别的原因吗?首先将在C99和C11中调用未定义的行为 在C99中,可以理解为:;由于缺少序列点,它们将调用未定义的行为 : 在上一个序列点和下一个序列点之间,对象的存储值最多应通过表达式的计算修改一次。此外,只有在确定要存储的值时,才能访问先前的值 说明:
#define swap(a, b) (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b)))
或
我看到了第二个宏,但不明白为什么它写得不像第一个宏?我错过了什么特别的原因吗?首先将在C99和C11中调用未定义的行为 在C99中,可以理解为:;由于缺少序列点,它们将调用未定义的行为 : 在上一个序列点和下一个序列点之间,对象的存储值最多应通过表达式的计算修改一次。此外,只有在确定要存储的值时,才能访问先前的值 说明:
第一个是在两个序列点之间修改
a
两次,因此根据语句,行为未定义:在上一个序列点和下一个序列点之间,对象的存储值最多应通过表达式的计算修改一次。就是这样(不需要考虑b
)
C11文档说明:
6.5表达式(p2):
如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用同一标量对象的值进行的值计算未排序,则行为未定义。如果一个表达式的子表达式有多个允许的顺序,则如果在任何顺序中出现这种未排序的副作用,则该行为是未定义的。84)
在(a)^=(b)^=(a)^=(b)
中,a
的副作用是不排序的,因此调用未定义的行为。应注意,C116.5 p1表示:
[…]运算符操作数的值计算在运算符结果的值计算之前排序
这保证了
(a) ^= (b) ^= (a) ^= (b)
| | | |
1 2 3 4
保证在最左边的^=
运算符的结果计算之前计算所有子表达式1、2、3和4。但是,这并不能保证在计算最左边的^=
运算符的结果之前,表达式3的副作用得到保证
一,。重点是我的。第一个变量在C99中调用有两个原因,最明显的原因是,不允许在同一个变量中多次修改同一个变量,并且该宏多次修改
a
和b
,而第二个宏使用:
它引入了一个序列点,但没有删除C99中所有未定义的行为,因为正在读取b
的先前值以计算a
的值,但只能用于确定要存储到b
的值
C99标准草案第6.5节的相关章节第2段(重点放在未来):
在上一个和下一个序列点之间,对象应具有其存储值
通过表达式的求值最多修改一次。72)此外,应仅读取先前值以确定要存储的值。.73)
对于逗号运算符,参见第6.5.17节逗号运算符第2段:
逗号运算符的左操作数计算为空表达式有一个
评估后的序列点[…]
为了更好地理解为什么第一个是未定义的,这里有另一种表示方法:
这是因为在C中,您无法控制子表达式之间的执行顺序:
a = a^(b=b^(a=a^b))
对于在=之后出现的第一个a,C编译器可以选择使用初始值a或修改后的值a。因此,它显然模棱两可,并导致未定义的行为
第二个在我看来还行,没有歧义:
b = b ^(a=a^b)
a和b出现在表达式
(a^b)&&&……
的第一部分这一事实对我来说似乎不是问题,因为&&强制首先计算第一部分。但是,我更愿意让专家来剖析标准,我不是专家…第二个标准中有一个,
。@内田:不要认为这有什么区别b^=a^=b
仍然是UB。@OliCharlesworth它实际上是UB吗?在C11中,在我看来,右侧b
的值计算将在a^=b
结果的值计算之前排序,因此在b
赋值之前排序。所以我不明白这是怎么回事。在C99中可能是UB。@interjay:在C99中肯定是UB。如果C11增加了更多的排序限制,这很好,但是在世界上大多数人使用C11之前,使用C99作为基线可能更安全。。。(或者,至少在回答中明确这一区别)@OliCharlesworth;好啊第二种方法不会在C99或C11中调用未定义的行为。之前的值只能用于确定要存储的值。(b)^=(a)^=(b)
@quamrana感谢格式化,而且@user3075488不是只有一个原因;即缺少序列点?@OliCharlesworth第二个原因是,a
和b
的先验值被用来确定要存储的值,这就是为什么添加逗号运算符并不能消除所有未定义的行为。但这是同样的原因;这导致UB,因为b^=a^=b
中没有足够的序列点。添加逗号运算符仍然不能添加足够的序列点。@OliCharlesworth这是一个公平的观点,但正如第二段代码所观察到的,它们是细微不同的原因,显然有人认为第二段代码删除了所有未定义的行为。@ShafikYaghmour;我认为我们错了。第二个也不会调用C99中未定义的行为。声明之前的值应为只读,以确定要存储的值。与鳕鱼无关
a = a^(b=b^(a=a^b))
b = b ^(a=a^b)