问题 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

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

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

所以,副作用 i++ (LHS)没有考虑到 i (RHS),它给出了未定义的行为。

第二个例子

i = ++i + 1; //! undefined
i = i + 1;   //  allowed

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

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

所以,执行 ++i + 1 应在更新的副作用之前 i,这意味着没有 标量对象的副作用相对于对同一标量对象的不同副作用或使用相同标量对象的值进行的值计算未经过排序。

用C99标准提出的术语和定义很容易解释这些例子(看相关问题)。但为什么呢 i = ++i + 1 根据C11的术语未定义?


2043
2018-03-24 17:09


起源



答案:


更新

我在这里改变我的答案,虽然它是在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
2018-03-24 17:24



++i 在C和C ++中有不同的含义。 - haccks
@haccks看起来我不正确,因为结果 ++i 不是改变语义的左值。 - Shafik Yaghmour
是。这就是我想说的:) - haccks
这是不是意味着 i = ++i + 1 在C ++中是否有明确的定义,而在C11中仍有未定义的行为?重写后 i = (i+=1) + 1 我们最终得到两个有序的分配,或者在添加之前测序的子表达,对吧?所以它也应该在C11中得到很好的定义。 - Krzysztof Abramowicz
@KrzysztofAbramowicz的主要区别在于,在C ++赋值中返回一个 lvalue 而在C11中它具体说 but is not an lvalue.。所以在C ++中它返回一个左值的事实需要一个 lvalue-to-rvalue 转换结果需要读取在C11中不存在的关系之前创建序列的结果。 - Shafik Yaghmour


答案:


更新

我在这里改变我的答案,虽然它是在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
2018-03-24 17:24



++i 在C和C ++中有不同的含义。 - haccks
@haccks看起来我不正确,因为结果 ++i 不是改变语义的左值。 - Shafik Yaghmour
是。这就是我想说的:) - haccks
这是不是意味着 i = ++i + 1 在C ++中是否有明确的定义,而在C11中仍有未定义的行为?重写后 i = (i+=1) + 1 我们最终得到两个有序的分配,或者在添加之前测序的子表达,对吧?所以它也应该在C11中得到很好的定义。 - Krzysztof Abramowicz
@KrzysztofAbramowicz的主要区别在于,在C ++赋值中返回一个 lvalue 而在C11中它具体说 but is not an lvalue.。所以在C ++中它返回一个左值的事实需要一个 lvalue-to-rvalue 转换结果需要读取在C11中不存在的关系之前创建序列的结果。 - Shafik Yaghmour


在您正确引用时,标准规定了分配(6.5.16)

更新左操作数的存储值的副作用是   在左右操作数的值计算之后排序。

(增量运算符没有区别,它只是伪装的赋值)

这意味着有两个值计算(左和右),然后在这些值之后对赋值的副作用进行排序。但它仅针对价值计算进行排序,而不是针对这些可能产生的副作用。所以最后我们面临着两个副作用 = 操作员和 ++ 运算符)彼此不是序列。


4
2018-03-27 19:32



我对该主题的最后一点想法正是朝这个方向发展......但我不确定这两个赋值解释是否实际上是对未定义行为的论证或反对:)你确定 价值计算 不包括任何 副作用 二手经营者暗示的?是 操作数评估, 价值计算 和 副作用执行 三个不同方面 表达评价,哪些受独立排序规则的约束? - Krzysztof Abramowicz
@KrzysztofAbramowicz如果没有明确指定,那么它是未定义的。 - Antti Haapala


但为什么呢 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 没有顺序。


3
2018-03-24 17:16



但我为什么要算LHS的呢 i 作为价值计算?当前值无关紧要,不会在任何地方使用。 - Krzysztof Abramowicz
LHS i 将在为其分配最终值之前对其进行评估。 - haccks
是什么 评测 实际意味着在这种背景下?在执行环境中, i 只是内存中的算术对象,甚至是寄存器 - 除了分配新值(这是副作用)之外还可以做什么呢? - Krzysztof Abramowicz
您需要在为其分配值之前获取其地址。 - haccks
@KrzysztofAbramowicz你给出的引用说,分配在之后排序 价值计算,而不是表达的副作用。这允许计算结果 ++i 没有执行存储计算值的副作用直到以后。 - ughoavgfhw