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构造函数后未初始化成员,读取未初始化的变量是未定义行为。
这个记忆之前被其他东西填满的事实是无关紧要的。
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 上试用这些示例。