为什么在此示例中成员未进行零初始化?
Why is a member not getting zero-initialized in this example?
这是专门针对 C++11 的:
#include <iostream>
struct A {
A(){}
int i;
};
struct B : public A {
int j;
};
int main() {
B b = {};
std::cout << b.i << b.j << std::endl;
}
使用 g++ 8.2.1 编译:
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:25:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << b.i << " " << b.j << std::endl
gcc 将 b.i
检测为未初始化,但我认为它应该与 b.j
.
一起进行零初始化
我相信正在发生的事情(特别是C++11,来自ISO/IEC工作草案N3337,强调我的):
B
不是聚合,因为它有基数 class。 Public base classes 只允许在 C++17 的聚合中使用。
A
不是聚合,因为它有一个用户提供的构造函数
第 8.5.1 节
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11),
no base classes (Clause 10), and no virtual functions (10.3).
b
正在使用空的 braced-init-list 初始化列表
第 8.5.4 节
List-initialization of an object or reference of type T is defined as follows:
— If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).
- 这意味着
b
得到值初始化
B
有一个隐式定义的默认构造函数,因此 b
值初始化调用零初始化
b.B::A
被零初始化,b.B::A.i
被零初始化,然后 b.B::j
被零初始化。
第 8.5 节
To zero-initialize an object or reference of type T means:
...
— if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
...
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the
default constructor for T is called (and the initialization is ill-formed if T has no accessible default
constructor);
— if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object
is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is
called.
但是,gcc 似乎只说 b.B::j
将被零初始化。为什么是这样?
我能想到的一个原因是,如果 B
被视为一个聚合,它会用一个空列表初始化 b.B::A
。
B
当然不是聚合,因为如果我们尝试使用聚合初始化,gcc 会正确地出错。
// ... as in the above example
int main() {
B b = {A{}, 1};
std::cout << b.i << " " << b.j << std::endl;
}
使用 C++11 编译
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:10:18: error: could not convert ‘{A(), 1}’ from ‘<brace-enclosed initializer list>’ to ‘B’
B b = {A{}, 1};
使用 C++17 编译
g++ -std=c++17 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:11:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << b.i << " " << b.j << std::endl;
我们可以看到 b.i
未初始化,因为 B
是一个聚合,而 b.B::A
正在被一个表达式初始化,该表达式本身使 A::i
未初始化。
所以这不是一个聚合。另一个原因是如果 b.B::j
进行零初始化,而 b.B::A
进行值初始化,但我在规范中的任何地方都看不到。
最后一个原因是是否调用了旧版本的标准。
来自 cppreference:
2) if T is a non-union class type without any user-provided constructors, every non-static data member and base-class component of T is value-initialized;
(until C++11)
在这种情况下,b.B::i
和 b.B::A
都将被值初始化,这将导致此行为,但它被标记为 ”(直到 C++11 )".
没有任何东西正在初始化 i
。它不会自动发生。您需要在 -class 或 class 构造函数的初始化列表中对其进行初始化。或者删除您的 non-trivial/user-defined 构造函数(或 = default
它,这使它变得微不足道)。
编译器正在使用您提供的构造函数,并且该构造函数不会初始化i
。
对于任何 class,如果只有一个用户定义的构造函数,则必须使用它,并且 A(){}
不会初始化 i
。
我也会遇到编译器错误。
- 我想我们都同意
b
得到值初始化 (8.5.4)
正在使用
value-initialize an object of type T means:
— if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
所以应该发生的是首先零初始化,然后可能会调用默认的ctors
- 以及定义:
To zero-initialize an object or reference of type T means:
— if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
因此应该发生以下情况:
- 用零填充
sizeof(B)
- 调用子对象
A
的构造函数,它什么都不做。
我认为这是优化中的错误。比较 -O0
和 -O1
的输出:https://godbolt.org/z/20QBoR. Without optimization the behaviour is correct. Clang on the other hand is correct in both: https://godbolt.org/z/7uhlIi
这个 "bug" 仍然存在于 GCC 中更新的标准标志:https://godbolt.org/z/ivkE5K
但是我假设在 C++20 中 B
是一个 "aggregate" 所以行为成为标准。
这是专门针对 C++11 的:
#include <iostream>
struct A {
A(){}
int i;
};
struct B : public A {
int j;
};
int main() {
B b = {};
std::cout << b.i << b.j << std::endl;
}
使用 g++ 8.2.1 编译:
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:25:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << b.i << " " << b.j << std::endl
gcc 将 b.i
检测为未初始化,但我认为它应该与 b.j
.
我相信正在发生的事情(特别是C++11,来自ISO/IEC工作草案N3337,强调我的):
B
不是聚合,因为它有基数 class。 Public base classes 只允许在 C++17 的聚合中使用。A
不是聚合,因为它有一个用户提供的构造函数
第 8.5.1 节
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
b
正在使用空的 braced-init-list 初始化列表
第 8.5.4 节
List-initialization of an object or reference of type T is defined as follows:
— If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1).
- 这意味着
b
得到值初始化 B
有一个隐式定义的默认构造函数,因此b
值初始化调用零初始化b.B::A
被零初始化,b.B::A.i
被零初始化,然后b.B::j
被零初始化。
第 8.5 节
To zero-initialize an object or reference of type T means:
...
— if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
...
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
— if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
但是,gcc 似乎只说 b.B::j
将被零初始化。为什么是这样?
我能想到的一个原因是,如果 B
被视为一个聚合,它会用一个空列表初始化 b.B::A
。
B
当然不是聚合,因为如果我们尝试使用聚合初始化,gcc 会正确地出错。
// ... as in the above example
int main() {
B b = {A{}, 1};
std::cout << b.i << " " << b.j << std::endl;
}
使用 C++11 编译
$ g++ -std=c++11 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:10:18: error: could not convert ‘{A(), 1}’ from ‘<brace-enclosed initializer list>’ to ‘B’
B b = {A{}, 1};
使用 C++17 编译
g++ -std=c++17 -pedantic-errors -Wuninitialized -O2 a.cpp
a.cpp: In function ‘int main()’:
a.cpp:11:25: warning: ‘b.B::<anonymous>.A::i’ is used uninitialized in this function [-Wuninitialized]
std::cout << b.i << " " << b.j << std::endl;
我们可以看到 b.i
未初始化,因为 B
是一个聚合,而 b.B::A
正在被一个表达式初始化,该表达式本身使 A::i
未初始化。
所以这不是一个聚合。另一个原因是如果 b.B::j
进行零初始化,而 b.B::A
进行值初始化,但我在规范中的任何地方都看不到。
最后一个原因是是否调用了旧版本的标准。 来自 cppreference:
2) if T is a non-union class type without any user-provided constructors, every non-static data member and base-class component of T is value-initialized; (until C++11)
在这种情况下,b.B::i
和 b.B::A
都将被值初始化,这将导致此行为,但它被标记为 ”(直到 C++11 )".
没有任何东西正在初始化 i
。它不会自动发生。您需要在 -class 或 class 构造函数的初始化列表中对其进行初始化。或者删除您的 non-trivial/user-defined 构造函数(或 = default
它,这使它变得微不足道)。
编译器正在使用您提供的构造函数,并且该构造函数不会初始化i
。
对于任何 class,如果只有一个用户定义的构造函数,则必须使用它,并且 A(){}
不会初始化 i
。
我也会遇到编译器错误。
- 我想我们都同意
b
得到值初始化 (8.5.4) 正在使用
value-initialize an object of type T means:
— if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.所以应该发生的是首先零初始化,然后可能会调用默认的ctors
- 以及定义:
To zero-initialize an object or reference of type T means:
— if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
因此应该发生以下情况:
- 用零填充
sizeof(B)
- 调用子对象
A
的构造函数,它什么都不做。
我认为这是优化中的错误。比较 -O0
和 -O1
的输出:https://godbolt.org/z/20QBoR. Without optimization the behaviour is correct. Clang on the other hand is correct in both: https://godbolt.org/z/7uhlIi
这个 "bug" 仍然存在于 GCC 中更新的标准标志:https://godbolt.org/z/ivkE5K
但是我假设在 C++20 中 B
是一个 "aggregate" 所以行为成为标准。