memset 之后的 C++ 放置新

Dan*_*tts 5 c++ memset placement-new undefined-behavior

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

struct Foo {
  int x;
  Foo() {}
}
Run Code Online (Sandbox Code Playgroud)

如果我将某个缓冲区设置为 0,则在该缓冲区上使用放置 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?
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*eyA 13

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

这个记忆之前被其他东西填满的事实是无关紧要的。

  • @AlexanderDyagilev - 好吧......直到你的程序在一个悲惨的布尔值上崩溃...... /sf/ask/3788460371/ (5认同)
  • @AlexanderDyagilev 你错了。读取未初始化的变量是教科书未定义的行为。 (3认同)
  • @AlexanderDyagilev 你是什么意思它会一直工作?UB 并不意味着无法编译。UB 意味着你无法知道行为会是什么。在这个例子中,placement new 可以将缓冲区初始化为某种调试表示,这意味着 `x` 的值可以在不同的实现中不同。此外,还有一个直接说明它是 UB 的标准:https://timsong-cpp.github.io/cppwp/basic#indet-2 (2认同)
  • @AlexanderDyagilev——“未定义的行为”并不意味着“会发生不好的事情”。它只是意味着 C++ 语言定义不会告诉您包含该行为的程序将做什么。通常,这样的程序将“工作”得很好。直到您向最重要的客户提供演示时,它才会崩溃。 (2认同)

Fra*_*ank 11

作为另一个答案的补充:

如果有人觉得将其视为“技术上未定义的行为,但对我来说足够安全”,请允许我演示结果代码的破坏程度。

如果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; 
}
Run Code Online (Sandbox Code Playgroud)

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

bar(void*):
        mov     DWORD PTR [rdi], 0   <----- memset(buff, 0, 4) or int x = 0? 
                                            They are redundant, so it's only done once.
        xor     eax, eax             <----- Set the return value to 0
        ret
Run Code Online (Sandbox Code Playgroud)

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

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

struct Foo {
  int x;
  Foo() {}
};
// ... same bar
Run Code Online (Sandbox Code Playgroud)

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

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

嗯,它当然更快,但是发生了memset()什么?

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

anything -> 0 -> anythinganything毕竟倒塌了。所以不改变指向的内存的函数buff是对代码的合理解释。

您可以在此处在 Godbolt 上试用这些示例。

  • 脚注:我*认为*编译器有权将 `eax` 保留为第二种情况。但是我可以看到返回存储在对象存储中的值如何与 gcc 为祖父合并添加的额外别名安全一致。 (2认同)