7

这是一个愚蠢的问题。:)

[编辑:愚蠢与否,这原来是一个 C++ 特殊问题,请参阅 UPDATE_2]

假设我们有:

int a = 0; // line 1
int b = ++a; // line 2

第 2 行发生的情况是(注意,数字只是标记,没有指定确切的顺序):

                      = [1: write result of (3) to result of (2)]
                     /\
[2: take "b" l-value] [3: convert result of (4) to an r-value ]
                      |
                      [4: take "a" l-value, "increment" and return it]

(4)中的“写”在(3)中的“读”之前“排序”,并且由于之间没有序列点,因此不能保证副作用发生在(3)之前(还有一个“ read" 在 (4) 本身内部,但在 "write"之前排序,因此不会产生 UB)。

那么,上面的错误在哪里?

[更新,针对没有经验的序列点律师:)]

换句话说,问题是:

  1. 无论是左值到右值的转换(“读取”)还是增量(“写入”)的副作用首先发生,似乎都存在“竞争”。

  2. 在 C 中,根据JTC1/SC22/WG14 N926“序列点分析” *,这将给出一个 UB(例如,参见示例 5 :) 。int x,y; (x=y) + x; // UB

  3. 请注意,如果使用后增量,则不会出现这种情况,因为 (3) 和 (4) 将构成单个[(3): 取“a”左值,将其转换为右值并返回该右值] “写”副作用延迟到下一个序列点之前的某个地方

_

(*) 这看起来像是 C99 标准委员会成员给出的主题的最清晰的系统原理。

[更新_2]

  1. 经验教训:永远不要用 C 规则来判断 C++ :))。我确实想知道为什么 N926(它清楚地描述了 C99 的方式)在产生 l 值的前增量的主题上“不够清楚”。

  2. 问题出现了,如何为 C++ 建立类似的基本原理,因为没有,因为即使在 C 案例中,仅解释标准也非常困难,而 C++ 语言标准要复杂得多且晦涩难懂。

[更新_3]

在“通用表达式的不确定性”中有一个处理一些相关主题的讨论(至少在较新的一半) 。,此外,委员会人员在这里讨论了此事(请参阅“222. 序列点和左值返回运算符”)。

4

2 回答 2

2

我认为解决方案可能是“++i”的措辞。它说“该值是操作数的新值;它是一个左值。”。并且行为在 5/4 中未定义为“此外,应仅访问先前值以确定要存储的值。”。

因此,我们访问的不是先前的值,而是新的值。然后我们可能会没事。不过,在未定义的行为和已定义的行为之间似乎只有一条很细的界限。

实际上,“先验值”在我看来就像“对象在前一个序列点的值”。如果这样解释,那么这个结构看起来是未定义的。但是如果我们直接比较 5.3/2 和 5/4 中的“++i”的措辞,我们就会面临“新价值”与“先验价值”的对比,事情是“弯曲”到定义的行为(“++i " 将在下一个序列点查看 "i" 的值,并生成该值作为 "++i" 的结果左值的内容)。

于 2010-03-10T21:13:12.707 回答
1

C++ 5/4 中的主要语句是

在前一个和下一个序列点之间,一个标量对象的存储值最多只能通过表达式的评估修改一次。

(约翰内斯引用的那句话是下面的,从属的。)

您怀疑哪个标量对象在此处多次修改其存储值? ab在第2行修改一次,所以没有问题。

(类似的语言在 C 标准的 6.5/2 中。)


编辑:

(4)中的“写”在(3)中的“读”之前“排序”,并且由于之间没有序列点,因此不能保证副作用发生在(3)之前

在重新阅读您的问题时,我认为这种困惑来自于一种困惑的思考方式++a:表达式并没有真正窥视a. 相反,将其视为“在您闲暇时(在下一个序列点之前)的++a返回a+1和副作用增量”可能会有所帮助。a

那么谁在乎这种副作用发生在 (3) 之前还是之后呢?表达式的值,也就是馈入 (3) 的值,已经确定。

于 2010-03-10T21:30:30.387 回答