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.
上试用这些示例
假设有一个构造函数没有初始化所有成员变量的结构:
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.
上试用这些示例