C++ 初始化中的求值顺序
在以下程序中:C++ 初始化中的求值顺序,c++,initialization,language-lawyer,aggregate-initialization,C++,Initialization,Language Lawyer,Aggregate Initialization,在以下程序中: #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
#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;
}
从3.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
在3.7版本中也使用了叮当声
在我自己的ubuntu上,gcc 6.2在构造intAA[3][3]={aa[2][2]=1}
时出现了内部编译器错误
我假设这是未定义的行为,但在标准中找不到明确的陈述
问题是:
初始值设定项列表(例如a[2]=1)中赋值的副作用评估顺序是否与标准中定义的数组实际元素(例如a[2]
)的初始化顺序相同
是否明确规定为已定义或未定义?还是仅仅因为它没有明确定义就没有定义
或者,除了计算顺序之外,构造是否由于其他原因而具有已定义或未定义的行为?让我们从最简单的情况开始:
这两个都是UB,因为违反了[basic.life]。在对象的生存期开始之前,您正在访问对象的值<代码>I
没有一个简单的默认构造函数,因此无法进行真空初始化。因此,对象的生命周期只有在构造函数完成后才开始。A
数组的元素在访问该数组的元素时尚未构造
因此,您通过访问尚未构造的对象来调用UB
现在,其他两种情况更为复杂:
参见,int
允许[basic.life]/1定义的“真空初始化”。已获取a
和aa
的存储。因此,int a[3]
是int
对象的有效数组,即使聚合初始化尚未开始。因此,访问对象,甚至设置其状态都是不必要的
这里的操作顺序是固定的。即使在C++17之前,初始化器列表元素的初始化也是在调用聚合初始化之前排序的,如[dcl.init.list]/4中所述。聚合中未在这里的初始化列表中列出的元素将像通过typename{}
构造一样填充int{}
表示初始化一个int
值,结果为0
因此,即使您设置了a[2]
和aa[2][2]
,它们也应该通过聚合初始化立即被覆盖
因此,所有这些编译器都是错误的。答案应该是:
当然,这一切都很愚蠢,你不应该这么做。但从纯语言的角度来看,这是一种定义明确的行为。我的眼睛在流血。。。如果这不是一次投票,我不知道会是什么。。。。如果你的代码乱七八糟,导致编译器的解析器崩溃,也会导致内部编译器错误。。。gcc 5+有一个小错误:在对象的生命周期开始之前使用对象有一个特殊的规定,这导致您可以在对象的构造函数启动(而不是完成)后立即访问对象成员。
0 0 1
1 0 0 0 0 0 0 0 1
1 2 2
1 2 2 2 2 2 2 2 2
I A[3] = {A[2].i = 1};
I AA[3][3] = {AA[2][2].i = 1};
int a[3] = {a[2] = 1};
int aa[3][3] = {aa[2][2] = 1};
1 0 0
1 0 0 0 0 0 0 0 0