我们如何预测以下C ++程序的输出

时间:2016-03-19 07:42:41

标签: c++ c++11 gcc g++

我对代码的输出感到困惑。 这取决于我运行代码的编译器。为什么会这样?

#include <iostream>
using namespace std;
int f(int &n)
{   
    n--;
    return n;
}
int main()
{
      int n=10;
      n=n-f(n);
      cout<<n;
      return 0;
}

使用g ++在Ubuntu终端上运行它,输出为1,而在Turbo C ++上运行它(我们在学校使用的编译器)将输出设为0。

1 个答案:

答案 0 :(得分:4)

在C ++ 03中,修改变量并在同一表达式中使用其值,而没有介入的C ++ 03 序列点未定义行为

C ++03§5/ 4:
  

之间   和下一个序列点标量对象的评估值最多只能修改一次   一个表达。此外,只能访问先前值以确定要存储的值。   对于完整的子表达式的每个允许排序,应满足本段的要求   表达;否则行为未定义。

Undefined Behavior,UB,为编译器提供了优化的机会,因为它可以假设UB不会出现在有效的程序中。

但是,有了C ++的所有无数UB规则,很难推断出源代码。

在C ++ 11中, C ++11§1.9/ 3

  

鉴于任何两项评估 A B ,如果    A B 之前排序,然后A的执行应先于 B 的执行。如果 A 之前没有排序    B B A 之前未排序,然后 A B < EM>未测序。 [注意:执行未经检测的   评估可以重叠。 -end note ]当 A 时,评估 A B   在 B B A 之前对其进行排序之前对其进行排序,但未指定哪个。

使用新的C ++ 11序列关系规则,相对于变量的使用,有问题的代码中函数的修改是不确定的,因此代码具有未指定的行为而不是未定义的行为,正如Eric {{M}在a comment to (the first version of) this answer中所指出的那样。基本上这意味着没有鼻子守护进程或其他可能的UB效应的危险,并且这种行为是合理的。这里的两个可能的行为是通过函数调用的修改在使用值之前完成,或者在使用值之后完成。

为什么这是未指明的行为:

C ++11§1.9/ 15:
  

调用函数中的每个评估(包括其他函数调用)都没有特别说明   在执行被调用函数体之前或之后对序列进行测序是不确定的   关于被调用函数的执行。

“未指明的行为”是指:

C ++11§1.3.25:
  

未指明的行为
  行为,对于格式良好的程序构造和正确的数据,取决于实现   [注意:不需要实现来记录发生的行为。可能的范围   行为通常由本国际标准划定。 -end note ]

为什么通过分配实施的修改不成问题:

C ++11§5.17/ 1
  

在所有情况下,分配都会在值之后排序   计算右和左操作数,并在赋值表达式的值计算之前。

这与C ++ 03完全不同。

正如这个答案的相当激烈的编辑所示,在Eric的评论之后,这种问题并不简单!我可以给出的主要建议是尽可能地让Say No™受到由细微或非常复杂的规则(语言的角落)支配的影响。简单的代码更有可能是正确的,而所谓​​的聪明的代码不太可能显着更快。