评估顺序和未定义的行为

时间:2013-07-01 08:26:06

标签: c++ c++11 sequence increment undefined-behavior

在C ++ 11标准的上下文中(不再有序列点的概念,如你所知)我想了解如何定义两个最简单的例子。

int i = 0;

i = i++;   // #0

i = ++i;   // #1

有关SO的两个主题可以解释C ++ 11上下文中的这些示例。 Here据说#0调用UB,#1定义明确。 Here有人说这两个例子都是未定义的。这种模棱两可让我很困惑。我已经三次阅读了这个结构良好的reference,但这个主题似乎对我来说太复杂了。

让我们分析示例#0i = i++;

相应的引言是:

  
      
  • 内置后增量和后减量的值计算   操作员在其副作用之前进行排序。

  •   
  • 内置的副作用(左参数的修改)   赋值运算符和所有内置复合赋值运算符   在计算值(但不是副作用)之后进行排序   左右参数,并在值之前排序   计算赋值表达式(即返回之前)   对修改对象的引用)

  •   
  • 如果标量对象的副作用相对于另一个没有排序   对同一个标量对象产生副作用,行为未定义。

  •   

当我得到它时,赋值运算符的副作用没有按其左右参数的副作用排序。因此,赋值运算符的副作用没有按i++的副作用排序。所以#0调用UB。

让我们分析示例#1i = ++i;

相应的引言是:

  
      
  • 内置预增量和预制的副作用   运算符在其值计算之前被排序(隐式规则到期)   定义为复合赋值)

  •   
  • 内置的副作用(左参数的修改)   赋值运算符和所有内置复合赋值运算符   在计算值(但不是副作用)之后进行排序   左右参数,并在值之前排序   计算赋值表达式(即返回之前)   对修改对象的引用)

  •   
  • 如果标量对象的副作用相对于另一个没有排序   对同一个标量对象产生副作用,行为未定义。

  •   

我看不出,这个例子与#0有什么不同。由于与#0相同的原因,这对我来说似乎是一个UB。赋值的副作用未按++i的副作用排序。它似乎是一个UB。上面讨论的主题说它定义明确。为什么呢?

问题 :如何应用引用规则来确定示例的UB。一个尽可能简单的解释将不胜感激。谢谢!

2 个答案:

答案 0 :(得分:9)

由于您的报价不是直接来自标准,我会尽量给出详细的答案,引用标准的相关部分。 “副作用”和“评价”的定义见第1.9 / 12段:

  

访问由volatile glvalue(3.10)指定的对象,修改对象,调用库I / O函数或调用执行任何这些操作的函数都是副作用,是执行环境状态的变化。表达式(或子表达式)的评估通常包括值计算(包括确定glvalue评估对象的标识并获取先前分配给对象以进行prvalue评估的值)和启动副作用。

下一个相关部分是第1.9 / 15段:

  

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的评估是不确定的。 [...]在运算符结果的值计算之前,对运算符的操作数的值计算进行排序。如果对标量对象的副作用相对于同一标量对象的另一个副作用或使用相同标量对象的值进行的值计算未被排序,则行为未定义。

现在让我们看看如何将其应用于这两个例子。

i = i++;

这是增量的后缀形式,您可以在第5.2.6段中找到它的定义。最相关的句子是:

  

在修改之前对++表达式的值计算进行排序   操作数对象。

对于赋值表达式,请参见第5.17段。相关部分指出:

  

在所有情况下,赋值在右和左操作数的值计算之后,以及赋值表达式的值计算之前进行排序。

使用上面的所有信息,整个表达式的评估是(标准不保证这个顺序!):

  • i++(右侧)的值计算
  • i(左侧)的值计算
  • 修改i++的副作用)
  • 修改i=的副作用)

所有标准保证是在赋值表达式的值计算之前对两个操作数的值计算进行排序。但是右手边的价值计算只是“读取i”的值,而不是修改i,这两个修改(副作用)没有按顺序排序相互之间,我们得到了不确定的行为。

第二个例子怎么样?

i = ++i;

这里的情况完全不同。您可以在第5.3.2节中找到前缀增量的定义。相关部分是:

  

如果x不是bool类型,则表达式++ x等效于x + = 1。

代替那个,我们的表达相当于

i = (i += 1)

在5.17 / 7中查找复合赋值运算符+=,我们得到i += 1等同于i = i + 1,但i仅评估一次。因此,所讨论的表达最终成为

  

i =(i =(i + 1))

但是我们已经从上面知道,=的值计算在操作数的值计算之后被排序,并且副作用在=的值计算之前被排序。因此,我们得到了明确的评估顺序:

  1. 计算i + 1的值(和i - 内在表达式的左侧)(#1)
  2. 启动内部=的副作用,即修改“内部”i
  3. 计算(i = i + 1)的值,这是i
  4. 的“新”值
  5. 启动外部=的副作用,即修改“外部”i
  6. 计算完整表达式的值。

  7. (#1):此处i仅评估一次,因为i += 1等同于i = i + 1,但i仅评估一次(5.17 / 7)

答案 1 :(得分:8)

关键区别在于++i被定义为i += 1,所以

i = ++i;

与:

相同
i = (i += 1);

由于+=运算符的副作用之前已经排序 运算符的值计算,实际修改 i++i的{​​{1}}在外部分配之前排序。这个 直接来自您引用的部分:“副作用 (内部赋值的修改) 运算符和所有内置复合赋值运算符 在计算值后排序(但不是副作用) 左右参数,并在之前排序 赋值表达式的值计算(即之前的 返回对修改对象的引用)“

这是由于嵌套赋值运算符; (外) 赋值运算符仅对值进行排序 计算其操作数,而不是它们的副作用。 (但是 当然,它不会撤消其他方面的排序。)

正如你间接指出的那样,这对C ++ 11来说是新的; 以前,两者都未定义。旧版本的C ++ 使用序列点,而不是之前排序,然后在那里 在任何赋值运算符中都没有序列点。 (一世 有这样的印象,意图是那些经营者 结果是左值有一个值在任何之后排序 副作用。在早期的C ++中,表达式*&++i是 未定义的行为;在C ++ 11中,它保证与...相同 ++i。)