在以下程序中:
#include <iostream>
struct I {
int i;
I(){i=2;}
I(int _i){i=_i;}
};
int a[3] = {a[2] = 1};
int aa[3][3] = {aa[2][2] = 1};
I A[3] = {A[2].i = 1};
I AA[3][3] = {AA[2][2].i = 1};
int main(int argc, char **argv) {
for (int b : a) std::cout << b << ' ';
std::cout << '\n';
for (auto &bb : aa) for (auto &b : bb) std::cout << b << ' ';
std::cout << '\n';
for (auto &B : A) std::cout << B.i << ' ';
std::cout << '\n';
for (auto &BB : AA) for (auto &B : BB) std::cout << B.i << ' ';
std::cout << '\n';
return 0;
}
输出
1 0 0
1 0 0 0 0 0 0 0 1
1 2 2
1 2 2 2 2 2 2 2 2
来自http://ideone.com/1ueWdK与clang3.7
但结果是:
0 0 1
1 0 0 0 0 0 0 0 1
1 2 2
1 2 2 2 2 2 2 2 2
在http://rextester.com/l/cpp_online_compiler_clang上还有clang 3.7。
在我自己的ubuntu上,gcc 6.2在构造int aa[3][3] = {aa[2][2] = 1}
上发出内部编译器错误。
我假设这是未定义的行为,但在标准中找不到明确的陈述。
问题是:
副标题的评估顺序是否影响初始化列表中的赋值(例如a[2] = 1
)和初始化标准中定义的数组的实际元素(例如a[2]
)?
明确说明是定义还是未定义?或者它是否因为未明确定义而变得未定义?
或者,由于评估顺序之外的其他原因,构造是否已定义或未定义行为?
答案 0 :(得分:4)
让我们从最简单的案例开始:
I A[3] = {A[2].i = 1}; I AA[3][3] = {AA[2][2].i = 1};
由于违反[basic.life],这两个都是UB。您在生命周期开始之前访问对象的值。 I
没有简单的默认构造函数,因此无法进行空白初始化。因此,对象的生命周期仅在构造函数完成后才开始。当您访问该数组的元素时,尚未构造A
数组的元素。
因此,您通过访问尚未构造的对象来调用UB。
现在,另外两个案例更复杂:
int a[3] = {a[2] = 1}; int aa[3][3] = {aa[2][2] = 1};
参见[{1}}允许“空的初始化”,由[basic.life] / 1定义。已获得int
和a
的存储空间。因此,aa
是int a[3]
个对象的有效数组,即使聚合初始化尚未开始。因此,访问对象甚至设置其状态不是UB。
此处的操作顺序是固定的。即使在C ++之前的版本17中,初始化程序列表元素的初始化也会在调用聚合初始化之前进行排序,如[dcl.init.list] / 4中所述。未在此处的初始化列表中列出的聚合中的元素将被填充,就像int
构造一样。 typename{}
表示对int{}
进行值初始化,结果为0。
因此,即使您设置了int
和a[2]
,也应立即通过聚合初始化覆盖它们。
因此,所有这些编译器都是错误的。答案应该是:
aa[2][2]
现在被授予,这都是非常愚蠢的,你不应该这样做。但从纯语言的角度来看,这是明确定义的行为。