【问题标题】:"Reading" a POD preincrement result does not yield undefined behavior. Why exactly?“读取” POD 预增量结果不会产生未定义的行为。为什么?
【发布时间】:2011-01-26 02:28:37
【问题描述】:

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

[编辑:愚蠢与否,结果证明这是一个 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)之前(有在 (4) 本身内部也是一个“读取”,但在 “写入”之前排序,因此不会产生 UB)。

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

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

换句话说,问题是:

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

  2. 在 C 中,将给出一个 UB,根据 JTC1/SC22/WG14 N926 "Sequence Point Analysis"*(参见示例 5:int x,y; (x=y) + x; // UB)。

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

_

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

[UPDATE_2]

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

  2. 1234563并且晦涩难懂。

[UPDATE_3]

"The undefinedness of a common expression." 讨论了一些相关主题(至少在 ~ 较新的一半),此外,委员会人员 here 讨论了此事(参见“222. 序列点和左值-返回运算符”)。

【问题讨论】:

  • 嗯,很有趣。这个问题只发生在 C++ 中,而不是 C 中(C 中的 ++a 不是左值)。
  • 感谢您的更新,这使问题变得更加有趣。显然我也不够老练,无法从您的 C 标签中猜出您只是在考虑 C++ 之后。
  • @John 那是 我的 错(以及导致问题本身的不一致,请参阅 update_2)。
  • 顺便说一句,我真的很喜欢你链接的论文。我只是在读它,它很有趣:)

标签: c++ c standards language-lawyer


【解决方案1】:

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

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

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

【讨论】:

  • 那么,您是否看到了一种扩展 N926 分析方法以支持相关 C++ 案例的方法?
  • 添加了一个 comp.lang.c 讨论链接,如果你有兴趣的话。
【解决方案2】:

C++ 5/4 中的主句是

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

(而约翰内斯引用的句子如下,从属。)

您怀疑哪个标量对象的存储值在这里被多次修改? ab在第2行各修改一次,所以没有问题。

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


编辑:

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

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

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

【讨论】:

  • @John,关键是当您在 C++ 中执行 ++a 时,它确实不会自动将 a+1 生成为一个独立于更改其存储值的值,因为那么它不能是左值。在 C++ 中,您可以执行诸如 int *p = &++i; 之类的操作,这在 C 中是不允许的。在 C++ 中,对于 ++a + 1 之类的内容,您修改了 a 的存储值,并在相同的序列点之间读取了它的值 不是确定要存储的值,而是进一步处理它。
  • @litb:确切地说,我正在更新我的帖子,实际上是你的评论超过了:))
  • 章节和诗句?因为我一直在阅读 5/4 和 5.3.2/2 (“该值是操作数的新值;它是一个左值”),我只是不认为这意味着重新读取了标量对象(在一个仿佛的世界)。
  • 这个措辞很不幸(因为它使用“值”,在 C 术语中表示“非左值”,但在 C++ 中并不意味着)。在 C++0x 中固定说“结果是更新的操作数。”。但由于它是一个左值,所以毫无疑问第二次读取。有关将左值转换为右值所采取的操作,请参见 4.1,其中包括访问存储在其中的值。如果操作数是左值但需要右值,则 5/8 负责触发该转换。
  • @JohannesSchaub-litb: 如果r 是一个映射到硬件寄存器的extern volatile,而i 是一个局部变量,那么需要i=++r; 来读取r,将该值加一存储到r,再次读取r,然后将该值存储到i?如果某些东西使用了++r 产生的值,那么第二次读取是否会发生?
猜你喜欢
  • 2018-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-03
  • 1970-01-01
  • 1970-01-01
  • 2021-11-10
相关资源
最近更新 更多