17

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;
};
构造 克++ 铿锵++
富() 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:编译器明显不同的行为的解释是什么?
4

1 回答 1

10

仅当类对象被零初始化时,填充位才会被清零,如您的报价中所述。

对于自动存储持续时间对象,仅当对象是值初始化并且它具有未删除的隐式默认构造函数且没有其他用户提供的默认构造函数时,才会发生零初始化。[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可能初始化它们之前也应该具有不确定的值。在这种情况下,如果没有零初始化,则检查它们的值会导致未定义的行为。

于 2022-02-03T23:23:49.587 回答