C11标准(ISO / IEC 9899:2011)在表达式(see related question)中引入了新的副作用测序定义。 序列点概念已经在之前对进行了排序,并且在关系之后进行了排序,现在是所有定义的基础。
第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标准(see related question)提供的术语和定义很容易解释这些示例。但是根据C11的术语,为什么i = ++i + 1
未定义?
答案 0 :(得分:6)
更新
我在这里改变我的答案,虽然它在C ++ 11中,但在C11中没有很好地定义。这里的关键是++i
的结果不是左值,因此在评估++i
之后不需要左值到右值的转换,因此我们无法确定++i
的结果1}}之后会被阅读。这与C ++不同,因此我最初链接的缺陷报告取决于这个关键事实:
[...]左值表达式++ i然后对结果进行左值到右值的转换。保证在计算加法运算[...]
之前对递增副作用进行排序
我们可以通过转到C11 draft standard部分6.5.3.1
前缀增量和减少运算符来看到这一点,其中包含:
[...]表达式++ E相当于(E + = 1)。[...]
然后是6.5.16
分配运营商部分(强调我的前进):
赋值运算符将值存储在左操作数指定的对象中。一个 赋值表达式具有赋值后的左操作数的值, 111 但不是左值。[...]
和脚注111
说:
允许实现读取对象以确定值,但不需要,即使对象具有volatile限定类型。
即使它是不稳定的,也无需读取对象来确定它的值。
原始答案
据我所知,这实际上定义得很好,这个例子已经从使用类似语言的C ++草案标准中删除了。我们可以在637. Sequencing rules and example disagree 中看到这一点:
以下表达式仍被列为未定义行为的示例:
i = ++i + 1;
然而,似乎新的测序规则使这个表达式定义明确:
并且解决方案是打击前缀示例并使用后缀示例,而这显然是未定义的:
更改1.9 [intro.execution]第16段中的示例,如下所示:
i =
++ ii ++ + 1; //行为未定义
答案 1 :(得分:4)
标准规定了分配(6.5.16),因为你正确引用
更新左操作数的存储值的副作用是 在左右操作数的值计算之后排序。
(增量运算符没有区别,它只是伪装的赋值)
这意味着有两个值计算(左和右),然后在这些值之后对赋值的副作用进行排序。但它仅针对价值计算进行排序,而不是针对这些可能产生的副作用。因此,最后我们面临着两个副作用(=
运算符和++
运算符),它们彼此之间没有序列。
答案 2 :(得分:3)
但根据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
的副作用未被排序。