C++ 在 memset 之后放置新的

C++ placement new after memset

假设有一个构造函数没有初始化所有成员变量的结构:

struct Foo {
  int x;
  Foo() {}
}

如果我将某个缓冲区 memset 为 0,在该缓冲区上使用 placement new 来创建 Foo 的实例,然后从该实例读取 x,这是已定义的行为吗?

void bar(void* buf) {
  memset(buf, 0, sizeof(Foo));
  Foo* foo = new(buf) Foo;
  std::cout << foo.x; // Is this undefined behavior?
}

这是教科书中未定义的行为。成员x在构造函数后没有初始化,读取未初始化的变量是未定义行为。

这个内存之前被其他东西填充的事实是无关紧要的。

作为另一个答案的补充:

如果有人认为“技术上未定义的行为,但对我来说足够安全”,请允许我演示如何彻底破坏生成的代码是。

如果x被初始化:

struct Foo {
  int x = 0;
  Foo() {}
};

// slightly simpler bar()
int bar(void* buf) {
  std::memset(buf, 0, sizeof(Foo));
  Foo* foo = new(buf) Foo;
  return foo->x; 
}

g++-11 与 -O3 产生以下结果:

bar(void*):
        mov     DWORD PTR [rdi], 0   <----- memset(buff, 0, 4) and/or int x = 0 
        xor     eax, eax             <----- Set the return value to 0
        ret

这很好。事实上,它甚至没有表现出人们希望通过就地未初始化构造消除的任何开销。编译器聪明.

与此相反,当 x 未初始化时:

struct Foo {
  int x;
  Foo() {}
};
// ... same bar

我们得到,使用相同的编译器和设置:

bar(void*):
        mov     eax, DWORD PTR [rdi] <----- Just dereference buf as the result ?!?
        ret

嗯,当然 更快 ,但是 memset() 怎么了?

编译器认为,由于我们将未初始化的 int(又名垃圾)放在新存储的内存之上,它甚至不必首先为 memset() 操心.它可以“回收”之前存在的垃圾。

anything -> 0 -> anything 最终崩溃为 anything。因此,不改变 buff 指向的内存的函数是对代码的合理解释。

您可以在 Godbolt here.

上试用这些示例