C++ 标准是否保证未初始化的 POD 成员在放置 new 后保留其先前的值?
根据 C++11,是否总是满足以下断言?
不。
未初始化的数据成员有一个不确定的值,这与说底层内存不存在完全不同。
[C++11: 5.3.4/15]:
创建类型对象的new表达式T
按如下方式初始化该对象:
- 如果省略new-initializer,则默认初始化对象(8.5);如果不执行初始化,则对象具有不确定的值。
- 否则,根据 8.5 的初始化规则解释new-initializer以进行直接初始化。
[C++11: 8.5/6]:
默认初始化类型的对象T
意味着:
- 如果
T
是(可能是cv 限定的)类类型(第 9 条),则调用的默认构造函数T
T
(如果没有可访问的默认构造函数,则初始化格式错误);
- 如果
T
是数组类型,则每个元素都是默认初始化的;
- 否则,不执行初始化。
[C++11: 12.1/6]:
一个默认且未定义为已删除的默认构造函数在被odr 使用(3.2) 创建其类类型 (1.8) 的对象时或在其第一次声明后显式默认时被隐式定义。隐式定义的默认构造函数执行类的一组初始化,这些初始化将由用户编写的该类的默认构造函数执行,没有ctor-initializer (12.6.2) 和空的复合语句。
[C++11: 12.6.2/8]:
在非委托构造函数中,如果给定的非静态数据成员或基类不是由mem-initializer-id指定的(包括由于构造函数没有ctor-initializer而没有mem-initializer-list的情况)并且实体不是抽象类(10.4)的虚拟基类,则
- 如果实体是具有大括号或相等初始化器的非静态数据成员,则实体按照 8.5 中的规定进行初始化;
- 否则,如果实体是变体成员(9.5),则不执行初始化;
- 否则,实体被默认初始化(8.5)。
(注意,第一个选项12.6.2/8
是如何beta
处理您的会员)
[C++11: 8.5/6]:
默认初始化类型的对象T
意味着:
- 如果
T
是(可能是cv 限定的)类类型(第 9 条),则调用 的默认构造函数(如果没有可访问的默认构造函数T
,则初始化格式错误);T
- 如果
T
是数组类型,则每个元素都是默认初始化的;
- 否则,不执行初始化。
[C++11: 8.5/11]:
如果没有为对象指定初始化器,则该对象是默认初始化的;如果不执行初始化,则具有自动或动态存储持续时间的对象具有不确定的值。
编译器可以选择在分配期间将底层内存清零(或以其他方式更改)。例如,众所周知,调试模式下的 Visual Studio 会将可识别的值0xDEADBEEF
写入内存以帮助调试;在这种情况下,您可能会看到0xCDCDCDCD
它们用来表示“干净的内存”(参考)。
在这种情况下会吗?我不知道。我不认为我们可以知道。
我们所知道的是 C++ 并没有禁止它,我相信这让我们得出了这个答案的结论。:)
C++03的答案是否相同?
是的,尽管逻辑略有不同:
[C++03: 5.3.4/15]:
创建类型对象的new表达式T
按如下方式初始化该对象:
- 如果省略了new-initializer:
- 如果
T
是(可能是cv-qualified)非 POD 类类型(或其数组),则该对象是默认初始化的(8.5)。如果T
是 const 限定类型,则基础类类型应具有用户声明的默认构造函数。
- 否则,创建的对象具有不确定的值。如果
T
是一个 const 限定类型,或者是一个(可能是cv 限定的)POD 类类型(或其数组),包含(直接或间接)一个 const 限定类型的成员,则程序是非良构的;
- 如果new-initializer的形式为
()
,则该项为值初始化(8.5);
- 如果new-initializer是形式
(expression-list)
并且T
是类类型,则调用适当的构造函数,将expression-list
其用作参数 (8.5);
- 如果new-initializer具有以下形式
(expression-list)
,并且T
是算术、枚举、指针或指向成员的指针类型并且expression-list
只包含一个表达式,则将对象初始化为表达式 (8.5) 的(可能转换的)值;
- 否则new-expression格式不正确。
现在,这一切都是我对初始化规则的严格解释。
实际上,我认为您看到与放置operator new
语法定义的潜在冲突可能是正确的:
[C++11: 18.6.1/3]:
备注:故意不执行其他动作。
下面的示例解释了放置new
“对于在已知地址构造对象很有用”。
但是,它实际上并没有谈论在已知地址构造对象而不混淆已经存在的值的常见用途,但是“不执行其他操作”这一短语确实表明意图是您的“不确定值”成为以前记忆中的任何东西。
或者,它可以简单地禁止操作员本身采取任何行动,让分配器自由。在我看来,标准试图提出的重要一点是没有分配新的内存。
无论如何,访问此数据会调用未定义的行为:
[C++11: 4.1/1]:
非函数、非数组类型的 glvalue (3.10)T
可以转换为纯右值。如果T
是不完整类型,则需要进行此转换的程序格式错误。如果泛左值所引用的对象不是类型对象,T
也不是派生自 的类型的对象T
,或者如果该对象未初始化,则需要此转换的程序具有未定义的行为。如果T
是非类类型,则纯右值的类型是 的 cv 非限定版本T
。否则,纯右值的类型是T
。
所以这并不重要:无论如何你都不能顺从地观察原始值。