在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
这可以通过定义轻松解释:
所以, i++
(LHS)的副作用与i
(RHS)没有任何关系,这给出了未定义的行为。
第二个例子
i = ++i + 1; //! undefined
i = i + 1; // allowed
然而,这个代码似乎在两种给定的情况下都会导致定义的行为:
因此, ++i + 1
的执行应该在更新i
的副作用之前,这意味着对于相对于相同标量对象上的不同副作用或使用值计算使用的未标定的标量对象,不存在副作用相同标量对象的值。
题
用C99标准提供的术语和定义很容易解释这些例子(参见相关问题)。 但为什么i = ++i + 1
根据C11的术语未定义?
更新
我在这里改变我的答案,这在C11中没有很好的定义,尽管它在C ++ 11中。 这里的关键是++i
的结果不是左值,因此在评估++i
之后不需要左值到右值的转换,所以我们不能保证++i
的结果将被读取之后。 这与C ++不同,因此我最初链接的缺陷报告取决于这个关键事实:
[...]左值表达式++ i,然后对结果执行左值到右值的转换。 保证增量副作用在加法运算的计算之前被排序[...]
我们可以通过转到C11草案标准部分来看到这一点6.5.3.1
前缀增量和减量运算符说:
[...]表达式++ E相当于(E + = 1)。[...]
然后是第6.5.16
节的作业操作员说(强调我的前进):
赋值运算符将值存储在由左操作数指定的对象中。 赋值表达式在赋值后具有左操作数的值, 但不是左值 。[...]
脚注111
说:
允许实现读取对象以确定值,但不要求即使对象具有易失性限定类型。
即使是易变的,也不需要读取对象来确定它的值。
原始答复
据我所知,这实际上已经很好的定义了,并且这个例子从使用相似语言的C ++草案标准中被删除。 我们可以在637中看到这一点。排序规则和示例不一致,其中说:
以下表达式仍然列为未定义行为的示例:
i = ++i + 1;
但是,新的排序规则似乎使这个表达式得到了很好的定义:
解决方案是打击前缀示例并使用后缀示例,而不是明确定义:
更改1.9 [intro.execution]第16段中的示例,如下所示:
我= ++我i ++ + 1; //行为未定义
但为什么i = ++i + 1
根据C11的术语未定义?
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
的副作用是不确定的。
该标准规定了分配(6.5.16),因为你引用正确
更新左操作数的存储值的副作用在左和右操作数的值计算之后被排序。
(增量运算符没有区别,它只是一个伪装的任务)
这意味着有两个值计算(左和右),然后在这些值之后对赋值的副作用进行排序。 但它只是针对价值计算进行排序,而不是针对这些可能产生的副作用。 所以最后我们面临着两个副作用( =
运算符和++
运算符),这些副作用不是相互关联的序列。