为什么 f(i=-1, i=-1) 是未定义行为?

281
我正在阅读关于求值顺序违规的内容,他们给出了一个让我感到困惑的示例。

1) If a side effect on a scalar object is un-sequenced relative to another side effect on the same scalar object, the behavior is undefined.

// snip
f(i = -1, i = -1); // undefined behavior
在这个上下文中,i是一个标量对象,显然意味着算术类型(3.9.1),枚举类型,指针类型,成员指针类型(3.9.2),std::nullptr_t以及这些类型的cv限定版本(3.9.3)被统称为标量类型。
我不明白在这种情况下该语句如何含糊不清。在我看来,无论是首个参数还是第二个参数先被评估,i最终都是-1,而且两个参数都是-1
有人可以澄清一下吗?

更新

非常感谢所有的讨论。到目前为止,我非常喜欢@harmic的答案,因为它揭示了定义这个语句的陷阱和复杂性,尽管乍一看它看起来很简单。@acheong87指出了在使用引用时出现的一些问题,但我认为这与此问题的无序副作用方面是不相关的。


摘要

由于这个问题引起了很多关注,我将总结主要观点/答案。首先,让我稍微离题一下,指出“为什么”可能有密切相关但略有不同含义,即“因为什么原因”,“为什么目的”。我将根据它们回答的“为什么”的含义分组。

因为什么原因

主要答案来自Paul DraperMartin J提供了类似但不够详细的答案。 Paul Draper的答案可以概括为

这是未定义的行为,因为未定义行为是什么。

总体而言,该答案在解释C++标准方面非常好。它还涉及到一些相关的未定义行为,例如f(++i, ++i);f(i=1, i=-1);。在第一个相关情况中,不清楚第一个参数是否应该是i+1,第二个参数是否应该是i+2或者反之;在第二个情况中,在函数调用后i是否应该是1或-1也不清楚。这两种情况都是未定义行为,因为它们属于以下规则:

如果对标量对象的副作用与同一标量对象上的另一个副作用无序,则行为未定义。

因此,f(i=-1, i=-1)也是未定义行为,因为它遵循相同的规则,尽管程序员的意图(在我看来)是明显且明确的。

保罗·德拉珀在他的结论中也明确表示:

它可能是已定义的行为吗?是的。它被定义了吗?没有。

这让我们想到了一个问题:“为什么会将f(i=-1, i=-1)定义为未定义行为?”

为什么会这样

虽然C++标准中有一些疏漏(可能是粗心),但很多省略都有明确的理由和特定目的。虽然我知道这个目的通常是“让编译器编写工作更轻松”或“生成更快的代码”,但我主要想知道为什么要把f(i=-1, i=-1)定义为UB。 harmicsupercat提供了主要答案,为UB提供了一个理由。Harmic指出,优化编译器可能会将表面上原子赋值操作分解成多个机器指令,并进一步交错这些指令以实现最佳速度。这可能会导致一些非常惊人的结果:在他的情况下,i最终变成-2!因此,harmic演示了如果操作未排序,则对变量多次赋相同的值可能会产生不良影响。

supercat提供了一个相关的阐述,说明试图让f(i = -1,i = -1)执行它看起来应该执行的操作的陷阱。他指出,在某些体系结构中,同时向同一内存地址进行多个写入是有严格限制的。如果我们处理的不是比f(i = -1,i = -1)更微不足道的东西,编译器可能很难捕捉到这一点。

davidf还提供了一个与harmic非常相似的交错指令的示例。

尽管harmic、supercat和davidf的例子都有些牵强,但综合起来仍然提供了一个具体的理由,说明为什么f(i=-1, i=-1)应该是未定义行为。
我接受了harmic的答案,因为它最好地解决了所有why的含义,尽管Paul Draper的答案更好地解决了“为什么”的部分。
其他答案 JohnB指出,如果考虑到重载赋值运算符(而不仅仅是普通标量),那么我们也可能遇到问题。

1
标量对象是标量类型的对象。参见3.9/9:“算术类型(3.9.1)、枚举类型、指针类型、成员指针类型(3.9.2)、std::nullptr_t以及这些类型的cv限定版本(3.9.3)统称为标量类型。” - Rob Kennedy
1
也许页面上有错误,他们实际上是指 f(i-1, i = -1) 或类似的东西。 - Mr Lister
请看这个问题:http://stackoverflow.com/a/4177063/71074 - Robert S. Barnes
1
你的更新应该在答案部分。 - Grijesh Chauhan
我认为的主要原因是f(i = -1, i = -2)是未定义的,如果两个值相等,没有真正的理由例外。这将使标准和编译器更加复杂,而几乎没有任何好处。 - Phil1970
显示剩余3条评论
11个回答

2
这是对“我不确定‘标量对象’除了像int或float之类的东西还能表示什么”的回答。
我认为“标量对象”是“标量类型对象”的缩写,或者只是“标量类型变量”。因此,指针、枚举(常量)都属于标量类型。
这是一个MSDN文章,标量类型

这篇回答有点像“仅链接答案”。你能否将那个链接中的相关部分复制到这个回答中(用块引用形式)? - Cole Tobin
1
@ColeJohnson 这不是一个仅含链接的答案。链接只是为了进一步解释。我的答案是“指针”,“枚举”。 - Peng Zhang
我并没有说你的回答是一个链接回答。我说它“读起来像是一个链接回答”。我建议你在帮助部分了解为什么我们不想要只有链接的答案。原因是,如果微软更新他们网站上的URL,那个链接就会失效。 - Cole Tobin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接