放置新的和未初始化的POD成员

Placement new and uninitialized POD members

本文关键字:初始化 POD 成员      更新时间:2023-10-16

C++标准是否保证未初始化的POD成员在新的放置后保留其以前的值?

或者更准确地说,根据C++11,以下断言是否总是满足的?

#include <cstdlib>
#include <cassert>
struct Foo {
int alpha; // NOTE: Uninitialized
int beta = 0;
};
int main()
{
void* p = std::malloc(sizeof (Foo));
int i = some_random_integer();
static_cast<Foo*>(p)->alpha = i;
new (p) Foo;
assert(static_cast<Foo*>(p)->alpha == i);
}

C++03的答案是一样的吗?

C++标准是否保证未初始化的POD成员在新的放置后保留其以前的值?

根据C++11,以下断言是否总是得到满足?

未初始化的数据成员有一个不确定的值,这与底层内存被单独留下完全不同。

[C++11: 5.3.4/15]:创建T类型对象的新表达式将该对象初始化如下:

  • 如果省略新初始化器,则对象为默认初始化的(8.5);如果不执行初始化,则对象的值不确定
  • 否则,新初始值设定项将根据8.5的初始化规则进行解释,用于直接初始化

[C++11: 8.5/6]:默认初始化T类型的对象意味着:

  • 如果T(可能cv合格)类类型,则T的默认构造函数被称为。(如果T没有可访问的默认构造函数,则初始化不正确)
  • 如果T是数组类型,则默认初始化每个元素
  • 否则,不执行初始化

[C++11: 12.1/6]:默认且未定义为已删除的默认构造函数是在odr使用(3.2)创建其类类型(1.8)的对象时,或在其第一次声明后显式默认时隐式定义的隐式定义的默认构造函数执行类的一组初始化由用户为该类编写的默认构造函数执行,该类没有ctor初始值设定项(12.6.2)和空的复合语句

[C++11: 12.6.2/8]:在非委托构造函数中,如果给定的非静态数据成员或基类不是由mem初始值设定项id指定的(包括由于构造函数没有ctor初始值设置项而没有mem初始项列表的情况),并且实体不是抽象类的虚拟基类(10.4),则

  • 如果实体是具有大括号或相等初始值设定项的非静态数据成员,则实体将按照8.5中的规定进行初始化
  • 否则,如果实体是变体成员(9.5),则不执行初始化
  • 否则,实体默认初始化(8.5)

(NB。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]:创建T类型对象的新表达式将该对象初始化如下:

  • 如果省略新初始值设定项
    • 如果T(可能是cv限定的)非POD类类型或其数组,则默认初始化对象(8.5)。如果T是const限定的类型,则底层类类型应具有用户声明的默认构造函数
    • 否则,创建的对象具有不确定的值如果T是const限定类型,或者(可能cv限定)POD类类型(或其数组)包含(直接或间接)const限定型的成员,则程序格式错误
  • 如果新初始值设定项的形式为(),则该项被值初始化(8.5)
  • 如果新初始值设定项的形式为(expression-list),并且T是类类型,则使用expression-list作为参数调用适当的构造函数(8.5)
  • 如果新初始值设定项的形式为(expression-list),而T是算术、枚举、指针或指向成员类型的指针,并且expression-list仅包括一个表达式,则对象被初始化为表达式(8.5)的(可能转换的)值
  • 否则,新表达式格式错误

现在,所有这些都是我对初始化规则的严格解释。

实际上,我认为你可能正确地看到了与放置operator new语法定义的潜在冲突:

[C++11: 18.6.1/3]:备注:故意不执行其他操作。

下面的一个示例解释了放置new"对于在已知地址构造对象可能很有用"。

然而,它实际上并没有谈到在已知地址构造对象而不混淆已经存在的值的常见用法,但短语"不执行其他操作"确实表明,其意图是让你的"不确定值"是以前记忆中的值。

或者,它可以简单地禁止运算符本身执行任何操作,让分配器自由执行。在我看来,确实标准试图提出的重要观点是没有分配新的内存。

无论如何,访问这些数据会调用未定义的行为:

[C++11: 4.1/1]:非函数、非数组类型T的glvalue(3.10)可以转换为prvalue。如果T是一个不完整类型,那么需要进行这种转换的程序就是格式错误的。如果glvalue引用的对象不是类型为T的对象,而不是从T派生的类型的对象,或者如果该对象未初始化,则需要此转换的程序具有未定义的行为。如果T是非类类型,则prvalue的类型是T的cv不合格版本。否则,prvalue的类型为T

所以这其实并不重要无论如何你都无法遵守原始值

C++11 12.6.2/8"初始化基地和成员"说:

在非委托构造函数中,如果给定的非静态数据成员或基类不是由mem初始值设定项id指定的(包括由于构造函数而没有mem初始值设定项列表的情况没有ctor初始值设定项),并且实体不是的虚拟基类一个抽象类(10.4),然后是

  • 如果实体是具有大括号或相等初始值设定项的非静态数据成员,则实体将按照8.5
  • 否则,如果实体是变体成员(9.5),则不执行初始化
  • 否则,实体默认初始化(8.5)

int上的默认初始化不起任何作用(8.5/6"Initializers"):

默认初始化T类型的对象意味着:

  • 如果T是(可能是cv限定的)类类型(第9条),则调用T的默认构造函数(初始化为如果T没有可访问的默认构造函数,则格式错误)
  • 如果T是数组类型,则默认初始化每个元素
  • 否则,不执行初始化

因此成员alpha应单独保留。