【发布时间】:2022-02-03 22:17:40
【问题描述】:
C++ 是否支持允许我们将对象及其所有填充字段初始化为零的语言结构。我在 cppreference.com 中发现了一些关于 zero-initialization 的令人鼓舞的措辞,这表明在某些情况下,填充字节也会被清零。
引自 cppreference.com:zero-initialization
以下情况会进行零初始化:
- 作为非类类型和没有构造函数的值初始化类类型成员的值初始化序列的一部分,包括未提供初始化器的聚合元素的值初始化。
零初始化的效果是:
- 如果 T 是标量类型,则对象的初始值是显式转换为 T 的整数常量零。
- 如果 T 是非联合类类型,则所有基类和非静态数据成员都初始化为零,并且所有填充都初始化为零位。构造函数(如果有)将被忽略。
- ...
您会在value-initialization、aggregate-initialization 和list-initialization 中找到对零初始化的引用。
我使用相当最新的 GCC 和 clang C++ 编译器进行了测试,它们的行为似乎有所不同。
坦率地说,我努力解析这些规则,特别是考虑到不同的编译器行为,我无法弄清楚如何正确解释这些规则。
参见代码here(至少需要 C++11)。结果如下:
给定:Foo
struct Foo
{
char x;
int y;
char z;
};
| Construct | g++ | clang++ |
|---|---|---|
| Foo() | x:[----][0x42][0x43][0x44],v: 0 |
x:[----][----][----][----],v: 0 |
y:[----][----][----][----],v: 0 |
y:[----][----][----][----],v: 0 |
|
z:[----][0x4A][0x4B][0x4C],v: 0 |
z:[----][----][----][----],v: 0 |
|
| Foo{} | 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:另见:does c initialize structure padding。是否适用?
- 问题 3:这些编译器是否符合标准?
- 问题 4:编译器明显不同的行为的解释是什么?
【问题讨论】:
-
你为什么不给它加标签c++或者language-lawyer?
-
在您的代码中,您还专门使用了 C++20。如果您不打算询问特定语言版本,我建议删除所有特定于版本的标签。
-
我相信零初始化仅适用于静态/线程存储持续时间对象。 Dynamic 和 Automatic 对象不会(默认情况下)将它们的填充清零)除非您明确将它们初始化为零,因为这是额外的运行时成本。
-
为什么要关心填充初始化?如果你依赖于特定的 padding 值,为什么不让 padding 显式成员,这样你就可以依赖对成员的标准要求和保证?毕竟,初始化填充是浪费 CPU 周期,这违反了 C++ 不为你不使用的东西付费的原则。
-
我会注意到您为编译器定义了
-O3。只要行为上没有明显的差异,编译器几乎可以做任何事情。填充可观察。
标签: c++ gcc initialization language-lawyer zero-initialization