C11表达式中的赋值运算符排序

Krz*_*icz 11 c expression undefined-behavior language-lawyer c11

介绍

C11标准(ISO/IEC 9899:2011)在表达式中引入了新的副作用测序定义(参见相关问题).该序列点概念已经补充了之前测序后序关系,这是现在所有定义的基础.

第6.5节"表达式",第2点说:

如果相对于对同一标量对象的不同副作用或使用相同标量对象的值进行值计算,对标量对象的副作用未被排序,则行为未定义.如果表达式的子表达式有多个允许的排序,则如果在任何排序中发生这种未测序的副作用,则行为是不确定的.

稍后,第6.5.16节"分配操作员",第3点指出:

在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序.对操作数的评估是不确定的.

问题

第一个引用的段落(6.5/2)由两个例子支持(与C99标准相同):

第一个例子

a[i++] = i;  //! undefined
a[i] = i;    //  allowed
Run Code Online (Sandbox Code Playgroud)

这可以通过以下定义轻松解释:

  1. 如果相对于(...)使用相同标量对象的值进行值计算,标量对象的副作用未被排序,则行为未定义.(6.5/2),
  2. 对操作数的评估是不确定的.[在作业内](6.5.16/3).

因此,i++(LHS)的副作用与i(RHS)无关,这给出了不确定的行为.

第二个例子

i = ++i + 1; //! undefined
i = i + 1;   //  allowed
Run Code Online (Sandbox Code Playgroud)

但是,此代码似乎在两种情况下都会导致定义的行为:

  1. 在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序.

因此,执行++i + 1 应该在更新的副作用之前i,这意味着相对于对同一标量对象的不同副作用或使用相同标量的值的值计算,对未标测的标量对象没有副作用宾语.

使用C99标准提出的术语和定义很容易解释这些例子(参见相关问题).但i = ++i + 1根据C11的术语,为什么不明确?

Sha*_*our 6

更新

我在这里改变我的答案,虽然它是在C++ 11中,但在C11中没有很好地定义.这里的关键是结果++i不是左值,因此在++i评估之后不需要左值到右值的转换,因此我们无法确定++i之后是否会读取结果.这与C++不同,因此我最初链接的缺陷报告取决于这个关键事实:

[...]左值表达式++ i然后对结果进行左值到右值的转换.保证在计算加法运算之前对增量副作用进行排序[...]

我们可以通过转到C11草案标准部分6.5.3.1 前缀增量和减量运算符来看到这一点:

[...]表达式++ E相当于(E + = 1).[...]

然后部分6.5.16 分配操作员说(强调我的前进):

赋值运算符将值存储在左操作数指定的对象中.赋值表达式在赋值后具有左操作数的值,111 但不是左值.[...]

和脚注111说:

允许实现读取对象以确定值,但不需要,即使对象具有volatile限定类型.

即使它是易失性的,也不需要读取对象来确定它的值.

原始答案

据我所知,这实际上是定义良好的,这个例子已从使用类似语言的C++草案标准中删除.我们可以在637看到这一点.排序规则和示例不同意说:

以下表达式仍作为未定义行为的示例列出:

i = ++i + 1;
Run Code Online (Sandbox Code Playgroud)

但是,似乎新的排序规则使这个表达式定义明确:

并且解决方案是打击前缀示例并使用后缀示例,而这显然是未定义的:

更改1.9 [intro.execution]第16段中的示例,如下所示:

i = ++ i i ++ + 1; //行为未定义