在初始化列表中使用两次递增变量 - 未定义行为?

17

编辑:尚未回答 - 链接的问题是关于普通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++ };

6
初始化列表中的各个组件没有相关的序列点,因此行为是未定义的。现在需要查找标准中说明此点的部分... - Jonathan Leffler
1
这个问题是为什么同时询问 [tag:c] 和 [tag:c++] 问题是一个坏主意的典型例子。它们是不同的语言,共享受限制的通用子语言和有限的ABI兼容性。 - Yakk - Adam Nevraumont
作为楼主,我认为揭示C/C++(不)兼容性更为晦涩的边角案例非常有用,比如这个。这就是为什么我问及两种语言的原因。 - Ray Hamel
@Yakk 或许我漏掉了什么,但据我所知我的问题已经得到回答,并附有引用 - 在 C 中是未定义的,在 C++11 中是明确定义的(从左到右评估)。这两个事情对于 C 程序员或那些在混合代码库上工作的人都是有帮助的;许多 C 编译器可能实现了 C++ 的行为,但不能依赖它。我可能不应该说我“主要感兴趣”C89,是的 - 我没有期望得到超过一两个回复。 - Ray Hamel
1
@RayHamel,有多个答案共同回答了你的问题。没有一个答案完全回答了你的问题。SO的目标之一是提出一个明确的答案来回答问题:一堆部分答案总比没有好,但并不理想。 - Yakk - Adam Nevraumont
显示剩余7条评论
3个回答

14

C++11及以后版本

对于列表初始化,其行为是明确定义的。根据序列化规则(自C++11以来):

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

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

引用自C++标准,$8.6.4/4 列表初始化 [dcl.init.list]

(强调我)

在大括号初始化列表的初始化器列表中,包括由可变参数扩展产生的任何初始化器子句在内,按照它们出现的顺序进行评估。也就是说,与给定初始化器子句相关的每个值计算和副作用都在逗号分隔的初始化器列表中后续的任何初始化器子句相关的每个值计算和副作用之前确定顺序。[注意:这种评估顺序不考虑初始化的语义;例如,当将初始化器列表的元素解释为构造函数调用的参数时,即使通常对于调用的参数没有排序约束,也适用。-注]

阅读实际标准时,很难确定该内容在哪里说明。C++11的§8.5节初始化器既冗长又难以理解。 - Jonathan Leffler
@JonathanLeffler 在标准中查找总是具有挑战性。 :) 无论如何,我找到了它并添加到答案中。 - songyuanyao
那一块是我找到的 - 它太长了,无法适应单个注释(因此我没有在注释中添加它)。之前的子部分也讨论了初始化(8.5.1到8.5.3,以及8.5本身)- 但由于问题中的语法使用了 {...},我猜想“列表初始化”是相关的部分。 - Jonathan Leffler
@JonathanLeffler 是的,我想问题集中在花括号初始化列表(C++11中的列表初始化)上。 - songyuanyao

13

C

在C语言中(不一定与C++相同),初始化程序列表的组成部分没有关联的序列点。

C11标准,ISO/IEC 9899:2011,在§6.7.9Initialization节中说:

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

151) 对于被覆盖且未用于初始化该子对象的任何子对象的初始化器可能根本未被评估。

听起来很有前途,但是…

¶23 初始化列表表达式的评估在相互之间具有不确定的顺序,因此任何副作用发生的顺序未指定。152)

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

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

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

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

C++

根据songyuanyao回答,C++11(以及更高版本)的规则与C11不同。这种情况强调了C语言和C++语言的差异,并使编写涵盖两种语言标记的问题的全面答案非常困难。

相关问题

至少还有两个与初始值设定之外的副作用相关的问题(例如 ++ )。它们都应该被阅读。特别是第二个问题对C++用户很有趣;第一个标记为C而不是C++,因此最相关的是那些对C感兴趣的人。

这两个问题都是由πάντα ῥεῖ在评论中指出的。


C99+TC2中的第23段没有提及顺序或顺序点,因此我的解释是OP代码是未定义行为。 - M.M
@M.M:在我手头的C99副本(原版)中,它写道:“_§23初始化列表表达式中任何副作用发生的顺序是未指定的。130) _”我查看了TC1、TC2、TC3和这段文字没有直接更改。严格地说,“未指定的”与“未定义的行为”不同,但最终结果实质上是相同的。 - Jonathan Leffler
对于 + 运算符的操作数,副作用的顺序也是未指定的。但如果其中多个副作用写入同一对象,则仍然未定义。它没有说明有一个序列点,因此我认为应该假定没有序列点。 - M.M

6
在C11中,所有这些初始化的行为都不是未定义的。请参见6.7.9/23:
初始化列表表达式的评估在彼此之间是不确定顺序的,因此任何副作用发生的顺序都是未指定的。
术语“不确定顺序”被定义为(5.1.2.3):
当A在B之前或之后排序时,评估A和B的顺序是不确定的。
在C99中,所使用的语言并没有清楚地说明是否情况相同,还是未定义的行为。在C89中根本没有提到这个问题,所以我们应该假设在C89中这些是未定义的。

嗯...但这也没有很好地定义,是吧?未指定的副作用顺序基本上也意味着UB,对吧? - Daniel Jour
@DanielJour 只有在副作用未排序时才会出现UB。但在这种情况下,它们是不确定排序的。我避免使用术语“定义良好”,因为有时人们使用它来表示甚至没有任何未指定的行为。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接