【问题标题】:Swapping Values with XOR [duplicate]用 XOR 交换值 [重复]
【发布时间】:2014-01-17 12:27:58
【问题描述】:

这两个宏有什么区别?

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

或者

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

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

【问题讨论】:

  • @quamrana 感谢格式化
  • 还有@user3075488

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


【解决方案1】:

First 将在 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。重点是我的。

【讨论】:

  • 第二个有,
  • @UchiaItachi:不要认为这有什么不同。 b ^= a ^= b 仍然是 UB。
  • @OliCharlesworth 真的是 UB 吗?在 C11 中,在我看来,右手b 的值计算将在a ^= b 结果的值计算之前排序,因此在分配给b 之前。所以我不明白它是怎样的UB。不过可能是 C99 中的 UB。
  • @interjay:绝对是 C99 中的 UB。如果 C11 增加了进一步的排序约束,那很好,但在世界上大多数人使用 C11 之前,使用 C99 作为基线可能更安全......(或者,至少在答案中明确区分)跨度>
  • @OliCharlesworth;好的。第二个不会在 C99 或 C11 中调用未定义的行为。the prior value shall be accessed only to determine the value to be stored 不能在 (b) ^= (a) ^= (b) 的情况下应用。
【解决方案2】:

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

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

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

C99 草案标准部分 6.5 Expressions 的相关部分第 2 段说(强调我的未来):

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

对于逗号操作符,来自6.5.17部分逗号操作符2说:

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

【讨论】:

  • 不就是一个的原因吗?即缺少序列点?
  • @OliCharlesworth 第二个原因是ab 的先前值被用于确定要存储的值,这就是为什么添加逗号运算符并不能消除所有未定义的行为。
  • 但这也是同样的原因;这会导致 UB因为b ^= a ^= b 中没有足够的序列点。添加逗号运算符仍然没有添加足够的序列点。
  • @OliCharlesworth 这是一个公平的观点,但正如第二段代码所观察到的那样,它们是微妙不同的原因,有人显然认为它删除了所有未定义的行为。
  • @ShafikYaghmour;我认为我们错了。第二个也不会在 C99 中调用未定义的行为。语句prior value 只能读取以确定要存储的值。(b) ^= (a) ^= (b)无关。
【解决方案3】:

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

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

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

第二个在我看来没问题,因为没有歧义:

b = b ^(a=a^b)

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-04
    • 1970-01-01
    • 1970-01-01
    • 2011-07-31
    • 2022-09-22
    相关资源
    最近更新 更多