C++ 标准是否保证非静态聚合对象的填充字节初始化为零?

Shr*_*m V 21 c++ gcc initialization language-lawyer zero-initialization

C++ 是否支持允许我们将对象及其所有填充字段初始化为零的语言构造。我在 cppreference.com 中发现了一些关于零初始化的令人鼓舞的措辞,表明在某些情况下,填充字节也将被清零。

引用自 cppreference.com: 零初始化

零初始化在以下情况下进行:

  1. 作为非类类型和没有构造函数的值初始化类类型的成员的值初始化序列的一部分,包括未提供初始化程序的聚合元素的值初始化。

零初始化的效果是:

  • 如果 T 是标量类型,则对象的初始值是显式转换为 T 的整数常量零。
  • 如果 T 是非联合类类型,则所有基类和非静态数据成员都初始化为零,并且所有填充都初始化为零位。构造函数(如果有)将被忽略。
  • ...

人们会在value-initializationaggregate-initializationlist-initialization中找到对零初始化的引用。

我使用相当最新的 GCC 和 clang C++ 编译器进行了测试,它们的行为似乎有所不同。

坦率地说,我努力解析这些规则,特别是考虑到不同的编译器行为,我无法弄清楚如何正确解释这些规则。

请参阅此处的代码(至少需要 C++11)。结果如下:

给出: Foo

struct Foo
{
    char x;
    int y;
    char z;
};
Run Code Online (Sandbox Code Playgroud)
构造 克++ 铿锵++
富() x:[----][0x42][0x43][0x44],v: 0 x:[----][----][----][----],v: 0
y:[----][----][----][----],v: 0 y:[----][----][----][----],v: 0
z:[----][0x4A][0x4B][0x4C],v: 0 z:[----][----][----][----],v: 0
富{} x:[----][----][----][----],v: 0 x:[----][0x42][0x43][0x44],v: 0
y:[----][----][----][----],v: 0 y:[----][----][----][----],v: 0
z:[----][----][----][----],v: 0 z:[----][0x4A][0x4B][0x4C],v: 0

这里[----]表示一个包含所有位0的字节,并且[0x..]是垃圾值。

正如您所看到的,编译器输出表明填充未初始化。和Foo()都是Foo{}值初始化。此外Foo{}还有一个聚合初始化,缺少初始化器。为什么零初始化规则没有被触发?为什么填充规则没有被触发?

我已经明白依赖填充字节为零不是一个好主意,甚至可能是未定义的行为,但我认为这超出了这个问题的重点。

  • 问题 1:标准是否提供了一种可靠地初始化填充字节的方法?
  • 问题2:另请参阅:c 是否初始化结构填充。是否适用?
  • 问题3:这些编译器符合标准吗?
  • 问题 4:如何解释编译器明显不同的行为?

use*_*522 12

仅当类对象被零初始化时,填充位才会被清零,如您的引用中所表达的。

对于自动和动态存储持续时间对象,仅当对象进行值初始化并且它具有未删除的隐式默认构造函数并且没有其他用户提供的默认构造函数时,才会发生零初始化。[dcl.init.general]/8.1此处满足这些条件。

值初始化应该始终与()初始值设定项一起发生。([dcl.init.general]/16.4

作为初始值设定项也可能发生值初始化{}。但是,如果类像此处一样是聚合,则首选聚合初始化,这不会导致值初始化。( [dcl.init.list]/3.4 )

在 C++14 之前, CWG 1301更改了聚合初始化相对于值初始化的偏好,这也可能适用于 C++11。C++11之前的规则可能有所不同,我没有检查过。


所以我想说 Clang 的行为是正确的,而 GCCFoo()在做不必要的工作时是错误的Foo{}(尽管正如 @PeterCordes 所指出的,将整个对象(包括填充)清零实际上更有效)。


请注意,我并不完全清楚检查非零初始化填充字节的值是否具有明确定义的行为。

对于默认初始化的情况,读取成员具有未定义的行为,因为它的值是不确定的。

我希望填充在可能初始化它们之前也应该具有不确定的值new。在这种情况下,如果没有零初始化,则检查它们的值将导致未定义的行为。