【问题标题】:Are there sequence points in the expression a^=b^=a^=b, or is it undefined?表达式a^=b^=a^=b中有序列点,还是未定义?
【发布时间】:2013-07-02 17:55:54
【问题描述】:

交换两个整数变量而不是使用临时存储的所谓“聪明”(但实际上效率低下)的方法通常涉及以下行:

int a = 10;
int b = 42;

a ^= b ^= a ^= b; /*Here*/

printf("a=%d, b=%d\n", a, b); 

但我想知道,像^= 这样的复合赋值运算符不是序列点,是吗? 这是否意味着它实际上是未定义的行为?

【问题讨论】:

  • 如果您编写的代码难以判断发生了什么,请问自己是否有更直接的方法可以让未来的开发人员理解?
  • 请注意,如果您在 C++ 代码中看到过这一点,C++ 对赋值运算符有不同的规则,允许在 C 中未定义的某些构造(我不确定这一点)。跨度>
  • @OliCharlesworth 我在那里投票决定关闭,因为在这里我们有更好的答案:)
  • @EdHeal,在我最初接触 C 的时候,我可能已经写过这样的代码,但多年来我已经知道得更多了。我把“聪明”放在引号里是有原因的。:-)

标签: c undefined-behavior sequence-points compound-assignment


【解决方案1】:

^= 运算符的求值顺序是明确定义的。没有明确定义的是ab的修改顺序。

a ^= b ^= a ^= b;

等价于

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

一个运算符在其参数被计算之前不能被计算,所以它肯定会先执行a ^= b

让这种行为成为未定义行为的原因是,为了让编译器在进行优化时有更大的灵活性,允许以它选择的任何顺序修改变量值。它可以选择这样做:

int a1 = a ^ b;
int b1 = b ^ a1;
int a2 = a ^ b1;
a = a1;
a = a2;
b = b1;

或者这个:

int a1 = a ^ b;
int b1 = b ^ a1;
a = a1;
int a2 = a ^ b1;
a = a2;
b = b1;

甚至这个:

int a1 = a ^ b;
int b1 = b ^ a1;
int a2 = a ^ b1;
a = a2;
a = a1;
b = b1;

如果编译器只能选择这三种方式中的一种来做事,这只是“未指定”的行为。但是,该标准更进一步,使其成为“未定义”行为,这基本上允许编译器假设它甚至不会发生。

【讨论】:

  • 第一个解释是终极的,但我无法理解原因:pre-modified or the post-modified values
  • 这是误导。在评估完成后,没有什么可以阻止表达式的副作用发生。 a ^= b 的结果是 a ^ b,作为副作用,a 被设置为该结果。 a 设置为未指定结果。特别是,没有什么要求它在 外部 a ^= ... 开始之前完成。
  • 这种解释的问题在于,它听起来像是编译器可以选择做一些可能的事情,而变量最终可能会得到令人惊讶的值。但由于行为实际上是未定义的,因此允许编译器执行任何操作。编译器可以有效地将a ^= b ^= a ^= b; 编译为printf("XORs are complicated!\n");,但实际上不会触及ab。 (更实际地,您会发现同一缓存行中的其他变量会被修改,从而产生奇怪的后果。)
  • 我不明白。 operator^= 返回对左操作数的引用。该标准在 5.17 [expr.ass] 中说,在所有情况下,赋值都是在左右操作数的值计算之后和赋值表达式的值计算之前进行排序的。 en.cppreference.com/w/cpp/language/eval_order 进一步说“赋值表达式的值计算”是指“返回对 modified 对象的引用”
【解决方案2】:
a ^= b ^= a ^= b; /*Here*/

这是未定义的行为。

您在两个序列点之间多次修改对象 (a)。

(C99, 6.5p2) "在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。

简单赋值和复合赋值都不会引入序列点。这里在表达式语句表达式之前和表达式语句之后有一个序列点。

序列点列在 c99 和 c11 标准的附录 C(资料性)中。

【讨论】:

    【解决方案3】:

    该表达式中没有序列点,因此它会产生未定义的行为。

    您可以通过使用逗号运算符来简单地修复它并保留大部分简洁性,确实引入了序列点:

    a ^= b, b ^= a, a ^= b;
    

    【讨论】:

    • 此时你不妨把它放在三个不同的行上。如果您关心这些东西,它将成为一个不错的盒子..
    【解决方案4】:

    ^= 不是序列点,是吗

    他们不是。

    这是否意味着它实际上是未定义的行为?

    是的。不要使用这种“聪明”的技巧。

    【讨论】:

    • 感谢您的确认。别担心,我一开始就没有打算使用它。
    猜你喜欢
    • 1970-01-01
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    相关资源
    最近更新 更多