为什么成员在此示例中未进行零初始化?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么成员在此示例中未进行零初始化?相关的知识,希望对你有一定的参考价值。

这特别是关于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不是聚合,因为它有一个基类。公共基类只允许在C ++ 17的聚合中使用。
  • A不是聚合,因为它有一个用户提供的构造函数

第8.5.1节

聚合是一个数组或类(第9节),没有用户提供的构造函数(12.1),非静态数据成员(9.2)没有大括号或相等的初始值,没有私有或受保护的非静态数据成员(子句) 11),没有基类(第10条),也没有虚函数(10.3)。

  • b正在使用空的braced-init-list初始化列表

第8.5.4节

列表初始化对象或类型T的引用定义如下: - 如果初始化列表没有元素且T是具有默认构造函数的类类型,则对象将进行值初始化。 - 否则,如果T是聚合,则执行聚合初始化(8.5.1)。

  • 这意味着b得到了价值初始化
  • B有一个隐式定义的默认构造函数,所以b值初始化调用零初始化
  • b.B::A得到零初始化,使b.B::A.i归零,然后b.B::j得到零初始化。

第8.5节

零初始化T类型的对象或引用意味着: ... - 如果T是(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化的,并且填充初始化为零位;

...

对值类型T的对象进行值初始化意味着: - 如果T是具有用户提供的构造函数(12.1)的(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的); - 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数。

然而,看起来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)如果T是不带任何用户提供的构造函数的非联合类类型,则T的每个非静态数据成员和基类组件都是值初始化的; (直到C ++ 11)

在这种情况下,b.B::ib.B::A都将进行值初始化,这会导致此行为,但标记为“(直到C ++ 11)”。

答案

我也会使用编译器错误。

  • 我想我们都同意b得到价值初始化(8.5.4)
  • 运用 value-initialize类型为T的对象意味着: - 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数。 所以应该首先进行零初始化,然后调用默认ctors
  • 并定义: 零初始化T类型的对象或引用意味着: - 如果T是(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化的,并且填充初始化为零位;

因此,应该发生以下情况:

  1. 用零填充sizeof(B)
  2. 调用没有任何作用的子对象A的构造函数。

我认为这是优化中的一个错误。将-O0的输出与-O1https://godbolt.org/z/20QBoR进行比较。没有优化,行为是正确的。另一方面,Clang在两个方面都是正确的:https://godbolt.org/z/7uhlIi

这个“bug”仍然存在于GCC中的新标准标志:https://godbolt.org/z/ivkE5K

但是我假设在C ++ 20中B是一个“聚合”,所以行为成为标准。

另一答案

对于任何类,如果有一个用户定义的构造函数,则必须使用它,并且A(){}不会初始化i

另一答案

什么都没有初始化i。它不会自动发生。您需要在类中或在类构造函数的初始化列表中初始化它。或者删除你的非平凡/用户定义的构造函数(或= default它,这使它变得微不足道)。

编译器正在使用您提供的构造函数,并且ctor没有初始化i

以上是关于为什么成员在此示例中未进行零初始化?的主要内容,如果未能解决你的问题,请参考以下文章

C:警告:“withdrawal_amt”可能在此函数中未初始化使用

AppSync 示例应用程序中未生成片段

参数类型不兼容且在此函数中未初始化

在构造函数代码之前禁用默认类成员初始化

为什么我不能在此片段中生成唯一对象数组?

为啥在默认成员初始化时内置类型的零成员? [复制]