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
这可以通过定义轻松解释:
- 如果相对于使用相同标量对象的值的(…)值计算,对标量对象的副作用未被排序,则行为未定义。 (6.5 / 2),
- 对操作数的评估是不确定的。 [在任务内](6.5.16 / 3)。
因此, i++
(LHS)的副作用与i
(RHS)无关,这给出了未定义的行为。
第二个例子
i = ++i + 1; //! undefined i = i + 1; // allowed
但是,此代码似乎在两种情况下都会导致定义的行为:
- 在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序。
因此, ++i + 1
的执行应先于更新i
的副作用,这意味着相对于对同一标量对象的不同副作用或使用的值计算,对未标定的标量对象没有副作用相同标量对象的值。
题
使用C99标准提供的术语和定义很容易解释这些例子( 参见相关问题 )。 但是根据C11的术语,为什么i = ++i + 1
未定义?
更新
我在这里改变我的答案,虽然它是在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;
但是,似乎新的排序规则使这个表达式定义明确:
并且解决方案是打击前缀示例并使用后缀示例而不是明确未定义:
更改1.9 [intro.execution]第16段中的示例,如下所示:
i =
++ ii ++ + 1; //行为未定义
在您正确引用时,标准规定了分配(6.5.16)
在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序。
(增量运算符没有区别,它只是伪装的赋值)
这意味着有两个值计算(左和右),然后在这些值之后对赋值的副作用进行排序。 但它仅针对价值计算进行排序,而不是针对这些可能产生的副作用。 所以最后我们面临着两个副作用( =
运算符和++
运算符),它们彼此之间没有序列。
但是根据C11的术语,为什么
i = ++i + 1
未定义?
C11表示左侧i
的副作用是有序的,但不是左右i
的值计算(评价)。
很明显,LHS的副作用将在评估LHS和RHS的表达后发生。
为了解释这个,可以有一个更好的例子
int i = 1; i = i++ + 3;
(首先让我们假设这个例子不会调用UB)。 现在i
的最终值可能是4
或2
。
案例1 。
左边的i
被取出然后它被递增并且3
被添加到它并且最后4
被分配给i
。
案例2 。
左边是i
,然后将3
添加到它,然后将4
分配给i
,最后i
增加。 在这种情况下, i
的最终值为2
。
尽管对左i
的副作用是有序的,但是没有定义存储到i
的最终值,即它不一定是由赋值,因此对i
的副作用是未定序的。