int i = f1() * f2() 的未定义行为

undefined behavior for int i = f1() * f2()

我很困惑为什么这会导致未定义的行为。我先把课本上的解释复制粘贴,然后展示我自己的代码和运行完美的程序。

Precedence specifies how the operands are grouped. It says nothing about the order in which the operands are evaluated. In most cases, the order is largely unspecified. In the following expression* int i = f1() * f2();:

*We know that f1 and f2 must be called before the multiplication can be done. After all, it is their results that are multiplied. However, we have no way of knowing whether f1 will be called before f2 or vice versa. For operators that do not specify evaluation order, it is an error for an expression to refer to and change the same object. Expressions that do so have undefined behavior (§ 2.1.2, p. 36). As a simple example, the << operator makes no guarantees about when or how its operands are evaluated. As a result, the following output expression is undefined.

-- C++ Primer - Page 193 by Stanley B. Lippman

所以,我尝试通过编写自己的代码来应用它,但我从未遇到过未定义的行为?有人可以解释一下这是什么意思吗?

#include <iostream>

using std::cout;
using std::endl;

int f1() { return (5 + 5 * 4 / 2 - 3); } // 12
int f2() { return (10 + 2 * 10 / 2 - 5); } // 15

int main()
{
    int i = f1() * f2();
    cout << i << endl;
    return 0;
}

来自 C++ 标准草案:

3.64 [defns.undefined] undefined behavior

behavior for which this document imposes no requirements [Note 1: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression ([expr.const]) never exhibits behavior explicitly specified as undefined in [intro] through [cpp]. — end note]

因此,几乎一切皆有可能,甚至是给定实现的可预测行为。

但是,您的程序没有显示任何 UB。 f1 和 f2 没有任何边框效果,因此它们的评估顺序没有影响。

你误会了。作者的意思是,如果顺序很重要,则未指定。在您的情况下,评估顺序无关紧要。事实上,函数也可以是 constexpr。但是如果你有这样的事情:

int i = 0;

int f1() { return (i++) * 3; }
int f2() { return (i++) * 4; }

int main() {
    int a = f1() + f2();
}

现在,如果先调用f1,则结果为4。如果先调用f2,则结果为3。因此,它是未指定的。


I never get an undefined behavior?

仅通过 运行 程序无法真正了解这一点。

您的代码没问题。

it is an error for an expression to refer to and change the same object

(大胆的矿)

您没有更改表达式中的任何对象,因此该规则不适用。

以下是规则何时适用的示例:

int a = 42;
int i = a++ * a++;

请注意,如果更改发生在函数中,它将适用:

int a = 42;
int foo() {return a++;}
int i = foo() * foo();

那是因为 UB 仅在 two accesses to an object are unsequenced relative to each other 时发生,即可以以任何顺序发生,包括并行发生。这不一定意味着“在并行线程中”,也可以意味着“单线程,但执行任务的处理器指令可能会交错”。

但是同一线程上的两个函数调用不能并行发生(并且它们的指令不能交错)。相反,在这种情况下,它们 不确定地排序 ,即一个严格地一个接一个,但未指定哪个是第一个。

另请注意

the << operator makes no guarantees about when or how its operands are evaluated

从 C++17 开始不再为真。