如果 f 修改 x,x*f(x) 的值是否未指定?

Is value of x*f(x) unspecified if f modifies x?

我看了一堆关于序列点的问题,一直无法弄清楚如果 f 修改 [=13] 是否保证 x*f(x) 的评估顺序=],这与 f(x)*x 不同吗?

考虑这段代码:

#include <iostream>

int fx(int &x) {
  x = x + 1;
  return x;
}

int f1(int &x) {
  return fx(x)*x; // Line A
}

int f2(int &x) {
  return x*fx(x); // Line B
}

int main(void) {
  int a = 6, b = 6;
  std::cout << f1(a) << " " << f2(b) << std::endl;
}

这会在 g++ 4.8.4 (Ubuntu 14.04) 上打印 49 42

我想知道这是有保证的行为还是未指定的行为。

具体来说,在这个程序中,fx 被调用了两次,两次都是 x=6,两次都是 returns 7。区别在于A行计算7*7(取fxreturns之后的x的值)而B行计算6*7(取x之前的值fx returns)。

这是有保证的行为吗?如果是,标准的哪一部分规定了这一点?

另外:如果我将所有函数更改为使用 int *x 而不是 int &x 并对调用它们的地方进行相应的更改,我得到 C 代码,其中包含同样的问题。 C 的答案有什么不同吗?

参数的求值顺序不是标准指定的,因此不能保证您看到的行为。

既然你提到了序列点,我会考虑使用该术语的c++03标准,而后来的标准改变了措辞并放弃了该术语。

ISO/IEC 14882:2003(E) §5 /4:

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified...


还有关于这是未定义行为还是仅仅是未指定顺序的讨论。该段的其余部分阐明了(或怀疑)这一点。

ISO/IEC 14882:2003(E) §5 /4:

... Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.

x 确实在 f 中被修改,它的值在调用 f 的同一表达式中被读取为操作数。并且没有指定 x 读取修改后的值还是未修改的值。这可能会向您尖叫 未定义的行为!,但不要着急,因为标准还规定:

ISO/IEC 14882:2003(E) §1.9 /17:

... When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function 11) ...

所以,如果f(x)先求值,那么复制返回值后就有一个序列点。所以上面关于 UB 的规则不适用,因为 x 的读取不在下一个和上一个序列点之间。 x 操作数将具有修改后的值。

如果x先求值,那么在f(x)的参数求值后有一个序列点同样,关于UB的规则不适用。在这种情况下 x 操作数将具有未修改的值。

总而言之,顺序未指定,但没有未定义的行为。这是一个错误,但结果在某种程度上是可以预测的。尽管措辞发生了变化,但在后来的标准中行为是相同的。我不会深入研究这些,因为它已经在其他好的答案中得到很好的涵盖。


既然你问了C的类似情况

C89(草案)3.3/3:

Except as indicated by the syntax 27 or otherwise specified later (for the function-call operator () , && , || , ?: , and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified.

这里已经提到了函数调用异常。如果没有序列点,以下是暗示未定义行为的段落:

C89(草案)3.3/2:

Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.26

这里是定义的序列点:

C89(草案)A.2

The following are the sequence points described in 2.1.2.3

  • The call to a function, after the arguments have been evaluated (3.3.2.2).

  • ...

  • ... the expression in a return statement (3.6.6.4).

结论与C++相同

在求值顺序上,更容易想到x*f(x)好像是:

operator*(x, f(x));

因此没有关于乘法应该如何工作的数学先入之见。

正如@dan04 所指出的,标准说:

Section 1.9.15: “Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.”

这意味着编译器可以自由地以任何顺序评估这些参数,序列点是 operator* 调用。唯一的保证是在调用 operator* 之前,必须评估两个参数。

在您的示例中,从概念上讲,您可以确定至少有一个参数为 7,但您不能确定它们都为 7。对我来说,这足以将此行为标记为未定义;然而,@user2079303 的回答很好地解释了为什么技术上不是这样。

无论行为是未定义的还是不确定的,都不能在行为良好的程序中使用这样的表达式。

Order of evaluation of the operands of almost all C++ operators is unspecified. The compiler can evaluate operands in any order, and may choose another order when the same expression is evaluated again

由于计算顺序并不总是相同,因此您可能会得到意想不到的结果。

Order of evaluation

关于我没有看到其他答案明确涵盖的内容的快速说明:

if the order of evaluation for x*f(x) is guaranteed if f modifies x, and is this different for f(x)*x.

考虑一下,如 Maksim 的回答

operator*(x, f(x));

现在只有两种方法可以根据需要在调用之前评估两个参数:

auto lhs = x;        // or auto rhs = f(x);
auto rhs = f(x);     // or auto lhs = x;
    return lhs * rhs

所以,当你问

I'm wondering whether this is guaranteed behavior or unspecified.

标准没有指定编译器必须选择这两种行为中的哪一种,但确实指定了那些是唯一有效的行为。

因此,既不能保证也不能完全确定。


哦,还有:

I've looked at a bunch of questions regarding sequence points, and haven't been able to figure out if the order of evaluation ...

序列点用于C语言标准的处理,但不用于C++标准。

你需要区分:

a) 运算符优先级和结合性,它控制子表达式的值由它们的运算符组合的顺序。

b) 子表达式求值的顺序。例如。在表达式 f(x)/g(x) 中,编译器可以先计算 g(x),然后再计算 f(x)。尽管如此,结果值当然必须通过按正确顺序划分各个子值来计算。

c) 子表达式的副作用序列。粗略地说,例如,为了优化,编译器可能决定仅在表达式末尾或任何其他合适的位置将值写入受影响的变量。

作为一个非常粗略的近似值,您可以说,在单个表达式中,求值顺序(不是结合性等)或多或少是未指定的。如果您需要特定的评估顺序,请将表达式分解为一系列语句,如下所示:

int a = f(x); int b = g(x); return a/b;

而不是

return f(x)/g(x);

有关确切规则,请参阅 http://en.cppreference.com/w/cpp/language/eval_order

在表达式 x * y 中,项 xy 未排序的 。这是三种可能的顺序关系之一,它们是:

  • A sequenced-before B: A 必须在 [=14= 之前完成所有副作用的评估] 开始评估g
  • AB不确定顺序:以下两种情况之一为真:A在[=之前顺序14=],或 BA 之前排序。 未指定这两种情况哪个成立。
  • A and B unsequencedAB之间没有定义顺序关系。

重要的是要注意这些是 成对 关系。我们不能说“x 未排序”。我们只能说两个操作相对于彼此是无序的。

同样重要的是,这些关系是transitive;并且后两个关系是对称的。


未指定 是一个技术术语,表示标准指定了一组可能的结果。这与 undefined behavior 不同,这意味着标准根本不涵盖该行为。 See here 进一步阅读。


转到代码 x * f(x)。这与 f(x) * x 相同,因为如上所述,在这两种情况下,xf(x) 相对于彼此 未排序

现在我们来到了几个人似乎要脱离困境的地步。计算表达式 f(x) 相对于 x 是未排序的 。但是,not 遵循 f 函数体内的任何语句相对于 x 也是无序的。事实上,任何函数调用都存在先后顺序关系,这些关系是不容忽视的。

这是来自 C++14 的文本:

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ] Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.

带脚注:

In other words, function executions do not interleave with each other.

粗体文本清楚地表明对于两个表达式:

  • A: x = x + 1; 里面 f(x)
  • B:计算表达式x * f(x)
  • 中的第一个x

它们的关系是:不确定的顺序

关于未定义行为和顺序的文本是:

If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, and they are not potentially concurrent (1.10), the behavior is undefined.

在这种情况下,关系是 不确定排序的 ,而不是未排序的。所以没有未定义的行为。

根据 x 是排在 x = x + 1 之前还是相反,结果 未指定 。所以只有两种可能的结果,4249.


如果有人对 f(x) 中的 x 有疑虑,则适用以下文本:

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function.

所以 x 的计算 顺序在 x = x + 1 之前。这是上面粗体引用中 "specifically sequenced before" 情况下的评估示例。


脚注:C++03 中的行为完全相同,但术语不同。在C++03中我们说每个函数调用的入口和出口都有一个序列点,所以函数内部对x的写和读是分开的x 在函数外至少有一个序列点。