Possible Duplicate:
How to initialize an array in C
array initialization, is referencing a previous element ok?
号
我想知道在C/C++标准中这样做是否安全?
1 2
| int a = 5;
int tab[] = { a , tab[0] + 1 , tab[1] }; |
它成功地编译和执行了GCC4.5和CLANG 2.9,但它总是真的吗?
打印此表将得到5 6 6。它在全局范围内初始化。
一般来说,C和C++都很有意思,但是我想在C++中使用它。
- C还是C++?选择一个。
- @一二三:不是真的。
- 为什么不直接写int tab[] = {a, a+1, a+1}?我不明白这一点。
- Tomalak,而我知道没有C/C++,最好还是知道这两个标准是怎么说的。
- @安德烈亚斯的问题当然不是如何最好地完成这一点,或者你如何能交替完成这一点,而是如果这是UB或者不是。这是一个非常有趣的问题,我自己也很想得到回答。
- @安德烈亚斯:重点是要知道在后面的列表初始化器中引用前一个元素初始化器的规则。看起来很清楚。
- 使用gcc和-pedantic,返回警告"initializer元素在加载时不可计算",并且数组未正确初始化。但是,使用g++,不会返回任何警告,并且正确执行初始化。我不知道标准的定义是什么。换句话说,C++中的工作在C中不起作用,但我通常认为它不安全。
- 您是否尝试用GCC不同的参数(STD=C99和-STD= C++ 03)来运行,以验证GCC使用不同的解析器变量输出相同的值吗?
- @亚历克斯-对结果感兴趣?:)请参阅下面的答案
<子> C++ 03/C++ 11答案< /Sub >
不,不会的。
在=的右侧,tab存在1,但如果它有自动存储时间,则它尚未初始化,因此您使用tab[0]和tab[1]使用的是未初始化的变量。
如果tab在名称空间范围内(因此具有静态存储持续时间,并且已初始化为零),那么这是"安全的",但是您使用tab[0]不会给您提供5。
很难提供这方面的标准参考资料,除了说明8.5"Initializers"中没有明确规定这一点的内容外,其他地方的规则也会补充其他内容。
1 [n3290: 3.3.2/1]: The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any) [..]
号
- 使用g++和clang的结果是5 6 6,所以我希望如此。
- @再次阅读我的答案。你不能保证这个结果。
- 它真的"安全"吗?动态初始化是否意味着对象被写入,所以不读取它来计算存储在不同数组元素中的值会导致ub,因为这两个操作都没有在另一个之前排序?
- @查尔斯:在初始化静态数据之前,可以安全地读取它,因为它已经初始化为零了。除非你指的是那个机制…?
- 埃多克斯1〔2〕
- 呃,不,我的意思是,在声明语句中,tab[0]是用一个新值写入的,然后读取它来确定要存储在tab[1]中的值。我不确定是否有"序列点"违规。我认为在列表初始化中没有问题,因为序列是明确指定的,但是我不确定聚合初始化。
- @查尔斯:你假设语义学,我刚刚写了一个完整的答案,说不存在。未指定在计算初始化器中的第二个项之前写入第一个元素。用有支撑的初始化器初始化数组使用列表初始化。
- 我理解您在评论中所说的是ub(我倾向于相信),因为标准没有在不同元素的初始化之间建立序列点。另一方面,你的回答指出,对于静态持续时间的物体,它是安全的,这与此相矛盾,也就是说,在静态持续时间的情况下,仍然存在相同的ub源。不是吗?
- @大卫:嗯,问得好。我明白查尔斯现在说的。我不太确定。我想应该是乌布……
- @查尔斯:我不确定8.5.4/4左右。它是否适用于聚合初始化?在计算下一个初始值设定项子句之前对值计算和副作用进行了排序,但是对于计算值的目标的实际初始化呢?
- 现在自言自语。
- @任何人:我在反驳,或者至少在我的第二条评论中挑战第三句话。
- @查尔斯:如果8.5.4/4确实适用于聚合初始化,我认为它不能保证问题中的代码,因为同一个子句用于初始化具有采用std::initializer_list的构造函数的类,在这种情况下,由于调用了构造函数,参数的构造必须在构造函数为ca之前完成。LLED。
1 2
| int a =5;
int tab[] = { a , tab[0] + 1 , tab[1] }; |
如果这些变量是在名称空间范围内声明的,那么它们是正常的,因为在名称空间范围内变量是零初始化的(由于静态初始化-请阅读本文了解详细信息)。
但是,如果它们在函数范围内声明,那么第二行调用未定义的行为,因为局部变量不是静态初始化的,这意味着tab[0]和tab[1]是未初始化的,用于初始化数组。读取未初始化的变量会调用未定义的行为。
- 所以你会得到一个0而不是一个5的tab[0]?
- @克里斯汀:如果是静态的,是的
- @托马拉克很有趣,你回答了这个评论,因为我看到了你的回答,然后想到了这个不完整。
- @克里斯蒂安:嘿:)
所以,现在我已经对你的问题进行了一些测试。
所有编译都是使用上面的示例代码执行的,使用以下方案:
1
| $(GCC) -o a.out test.c -Wall -Wextra -pedantic -std=$(STD) |
号
结果如下:
对于GCC = gcc,标准-std=c89; -std=iso9899:1990; -std=iso9899:199409; -std=gnu89导致出现警告:initializer element is not computable at load time,运行时行为不明确,意味着数组的第二和第三个值是随机垃圾。
标准-std=c99; std=iso9899:1999; -std=gnu99没有产生这种警告,但在运行时也显示出未定义的行为。
对于GCC = g++,标准-std=c++98; -std=gnu++98; -std=c++0x没有产生任何警告,代码按照您预期的方式工作,从而产生一个包含{5, 6, 6}值的数组。
然而,正如大多数人建议的那样,使用它可能是不明智的,因为您的代码在其他编译器上的行为可能不同,甚至在同一个编译器的其他版本上,这通常是一件坏事:)
希望有帮助。
- 请问,你是如何发现未定义的行为的?
- 顺便问一句,这不仅仅是关于"其他编译器,甚至可能是同一个编译器的其他版本",而是关于同一个编译器的不同运行方式;或者也许它只在您的情况下工作,但实际上根本没有,而是在宇宙中创造了一个手指大小的洞……?
- @tomalak i printf()显示数组的内容。这是一种非常可靠的检测UDIMO的方法,我只是陈述了实验结果,没有任何完整性的声明。如果您找到了一个g++创建可执行文件的示例,其中上面问题的声明不会产生预期的结果,请告诉我:)-我的猜测是,g++检测到此类初始化并适当地处理它们。但是,正如我所说,我不会依赖它,而且绝对不会使用它。
- 在许多情况下,数学上不可能"检测"ub。当然,printfing something绝不是ub检测工具。
- 当然,准确的检测通常是不可能的。但我们这里的情况非常简单,在某种程度上,如果发生了什么错误,所有其他可能的"错误"来源都被消除了,那么就可以识别出未定义的行为。当发生错误时,它不是ub的概率是微不足道的,因此可以通过运行至少两次的每个测试几乎完全消除。
- 感谢您的快速分析,尽管这并没有说明代码的标准一致性。如果不同的编译器设置产生不同的输出,我认为避免使用这种初始化是可行的。然而,我认为在haskell中,定义无限数组是很正常的(因为它是懒惰的计算)。+ 1
在C99标准中,似乎可以保证成员的初始化顺序:
§6.7.8/17: Each brace-enclosed initializer list has an associated current object. When no designations are present, subobjects of the current object are initialized in order according to the type of the current object: array elements in increasing subscript order, structure members in declaration order, and the first named member of a union. In contrast, a designation causes the following initializer to begin initialization of the subobject described by the designator. Initialization then continues forward in order, beginning with the next subobject after that described by the designator.
号
但是正如@tomalak在注释中提到的那样,这并不能为操作提供完全的保证,因为编译器将首先评估所有参数,然后按照前面的顺序应用结果。也就是说,前一个报价没有在初始化tab[0]和评估用于初始化tab[1]的表达式tab[0]+1之间施加顺序(它只在初始化tab[0]和tab[1]之间施加顺序)。
至于C++标准,既不在当前标准中,也不在即将到来的C++ 0x标准的FDIS中,似乎有一个特定的子句定义了初始化的顺序。唯一提到订购的是
§8.5.1/2 When an aggregate is initialized the initializer can contain an initializer-clause consisting of a brace-enclosed, comma-separated list of initializer-clauses for the members of the aggregate, written in increasing subscript or member order.
号
但这只与初始值设定项中的条目的写入顺序有关,而与实际计算方式无关。
- c99引号不能保证操作数计算在这个顺序初始化过程中发生。
是的-它可能会像你期望的那样工作。不,(你没有问,但是)不要用它,它没有逻辑,这是一个坏做法。