【问题标题】:Incrementing a variable used twice in an initializer list - undefined behavior?增加初始化列表中使用两次的变量 - 未定义的行为?
【发布时间】:2016-09-06 03:04:13
【问题描述】:

编辑:尚未回答 - 链接的问题是关于普通 r 值的,如果相关的话,初始化列表是一个单独的概念。

这个语句是否定义良好,或者在初始化列表中使用前缀增量运算符,在列表中出现两次的变量上,未定义的行为?

struct T t = { i, ++i };

我对 ANSI C 最感兴趣,但了解其他版本的 C 和/或 C++ 是否不同也会很有用。如果像下面这样的类似结构是合法的:

struct T t = { i, i++ };

struct T t = { ++i, ++i };

struct T t = { i++, ++i };

struct T t = { i++, i++ };

【问题讨论】:

  • 没有与初始化列表的组件关联的序列点,因此行为未定义。现在去寻找标准中说...的部分......
  • @πάντα ῥεῖ:嗯……那个重复的问题 (Why are these constructs (using ++) undefined behaviour?) 涵盖了很多领域,但是 AFAICS,它没有涵盖初始化的顺序,这可能会有所不同。具体来说,涵盖该行为的标准部分与该副本的任何答案中引用的任何部分都不相同。
  • @JonathanLeffler 我认为这是更好的选择:stackoverflow.com/questions/4176328/…:我们应该重新打开/关闭吗?
  • 这个问题很好地说明了为什么问cc++ 问题是个坏主意。它们是不同的语言,共享受限的公共子语言和有限的 ABI 兼容性。
  • @RayHamel 有多个答案共同回答了您的问题。没有一个答案回答了您的整个问题。 SO 的目标之一是产生可以用一个确定性答案回答的问题:部分答案总比没有好,但并不理想。

标签: c++ c undefined-behavior initializer


【解决方案1】:

C++11 及更高版本

list initialization 的行为是明确定义的。根据sequenced-before rules(C++11 起):

10) 在列表初始化中,给定初始化器子句的每个值计算和副作用都排在与任何初始化器子句相关联的每个值计算和副作用之前,该初始化器子句在括号括起来的逗号分隔的初始化器列表中。

所以对于struct T t = { i, ++i };,首先评估i,然后评估++i,顺序是明确定义的。所有其他样本也可以。

引用 C++ 标准,$8.6.4/4 List-initialization [dcl.init.list]

(强调我的)

在花括号初始化列表的初始化列表中, 初始化子句,包括任何由包扩展产生的子句 ([temp.variadic]),按照它们出现的顺序进行评估。 也就是说,与 给定的初始化子句在每个值计算之前排序 以及与它后面的任何初始化子句相关的副作用 在初始化列表的逗号分隔列表中。 [注:这 无论语义如何,求值顺序都成立 初始化;例如,它适用于 initializer-list 被解释为构造函数调用的参数, 即使通常没有排序限制 调用的参数。 ——尾注]

【讨论】:

  • 阅读实际标准,很难弄清楚它是在哪里声明的。第 8.5 节 C++11 的初始化程序既冗长又难以阅读。
  • @JonathanLeffler 在标准中查找总是具有挑战性的。 :) 无论如何,我找到它并添加到答案中。
  • 那块是我发现的——它太长了,不能放在一个评论中(所以我没有在 cmets 中添加它)。前面的小节也讨论了初始化(8.5.1 到 8.5.3,加上 8.5 本身)——但由于问题中的语法使用 { … },我猜“列表初始化”是相关部分。
  • @JonathanLeffler 是的,我想问题集中在括号初始化列表(C++11 中的列表初始化)。
【解决方案2】:

C

在 C 中(不一定与 C++ 的答案相同),没有与初始化列表的组件相关联的序列点。

C11 标准 ISO/IEC 9899:2011 在第 §6.7.9 节 初始化

¶19 初始化应按初始化程序列表顺序进行,为特定子对象提供的每个初始化程序都将覆盖同一子对象的任何先前列出的初始化程序; 151)

151) 任何被覆盖的子对象的初始化器都可能根本不被评估。

这听起来很有希望,但是……

¶23 初始化列表表达式的计算相对于彼此的顺序是不确定的,因此未指定任何副作用发生的顺序。152)

152) 特别是,评估顺序不必与子对象初始化的顺序相同。

因此,(在 C 中)计算的顺序是不确定的,并且您不能依赖增量发生的时间(或者,在问题中的代码未说明的极端情况下,增量是否发生)。

在 C99 (ISO/IEC 9899:1999) 中,章节编号为 §6.7.8,但第 19 段和第 23 段内容基本相同,只是脚注编号不同。

在 C90 (ISO/IEC 9899:1990) 中,该问题未明确解决。

C++

songyuanyaoanswer来看,C++11(及更高版本)中的规则与C11中的规则不同。这类事情强调了语言 C 和 C++ 是不同的,这使得为用这两种语言标记的问题编写全面的答案非常困难。

密切相关的问题

在初始化器之外的上下文中,至少还有两个与副作用相关的问题(例如++)。他们都应该被阅读。第二个,特别是 C++ 用户感兴趣的;第一个标记为 C 而不是 C++,因此与对 C 感兴趣的人最相关。

πάντα ῥεῖ 在 cmets 中指出了两者。

【讨论】:

  • C99+TC2 中的第 23 段没有提到排序或排序点,所以我的解释是 OP 代码是未定义的行为。
  • @MM:在我的 C99(原始)副本中,它说:§23 初始化列表表达式中任何副作用发生的顺序未指定。130) .我查看了 TC1、TC2、TC3 中的每一个,并且此文本没有直接更改。严格来说,“未指定”与“未定义行为”不同,但我认为最终结果基本相同。
  • + 的操作数的副作用顺序也未指定。但是,如果多个副作用写入同一个对象,它仍然是未定义的。它没有说任何地方都有序列点,所以应该假设没有恕我直言。
【解决方案3】:

在 C11 中,所有这些初始化的行为都不是未定义的。见 6.7.9/23:

初始化列表表达式的计算相对于彼此的顺序是不确定的,因此任何副作用发生的顺序都是未指定的。

indeterminately sequenced 一词是这样定义的(5.1.2.3):

当 A 被排序时,评估 A 和 B 的排序不确定 在B之前或之后,但未指定哪个。

在 C99 中,所使用的语言并没有明确说明是相同的情况还是未定义的行为。在 C89 中根本没有提到这个问题,所以我们可能应该假设在 C89 中这些是未定义的。

【讨论】:

  • 嗯......但它也没有很好的定义,或者?副作用的未指定顺序基本上也意味着 UB 吗?
  • @DanielJour 只有当副作用是unsequenced时它才是UB。但在这种情况下,它们是不确定排序的。我避免使用“定义明确的”一词,因为有时人们用它来表示甚至不是任何未指定的行为。
猜你喜欢
  • 1970-01-01
  • 2020-02-12
  • 2016-01-28
  • 1970-01-01
  • 2016-07-31
  • 2021-09-07
  • 1970-01-01
  • 2013-10-10
  • 1970-01-01
相关资源
最近更新 更多