介绍
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标准提出的术语和定义很容易解释这些例子(看相关问题)。但为什么呢 i = ++i + 1 根据C11的术语未定义?
更新
我在这里改变我的答案,虽然它是在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 ++ + 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 ++ + 1; //行为未定义
在您正确引用时,标准规定了分配(6.5.16)
更新左操作数的存储值的副作用是
在左右操作数的值计算之后排序。
(增量运算符没有区别,它只是伪装的赋值)
这意味着有两个值计算(左和右),然后在这些值之后对赋值的副作用进行排序。但它仅针对价值计算进行排序,而不是针对这些可能产生的副作用。所以最后我们面临着两个副作用 = 操作员和 ++ 运算符)彼此不是序列。
但为什么呢 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 没有顺序。