如果我有这样的代码,在 C 中是合法的:
int* i = static_cast<int*>(std::malloc(sizeof(int)));
*i = 123;
这也是合法的 C++,还是会调用未定义/实现定义的行为?对于哪些类型是合法的?
或者在这种情况下是否需要做
new(i) int (123);
?
例如,对于非平凡的类,它不可能是合法的,因为该对象从未被构造,但随后它的
operator=
被调用。
从slab分配器的角度来看,你的方法看起来是合法的C++,前提是你在使用
new[]
运算符构造对象本身时观察到显着的性能下降,并且只想为 maybe 的对象分配一块内存被创建,并且不希望有一堆 new/malloc/系统内存调用,以避免上下文切换
按照您的思维过程,您可以执行以下操作:
struct SomeStruct {
SomeStruct() {
// Your initialization logic
}
int val;
};
void* mem = std::malloc(sizeof(SomeStruct) * 3);
SomeStruct* start = static_cast<SomeStruct*>(mem);
// Suppose you want to create an object at index 1 of allocated memory
SomeStruct* ptr = new (&(start[1]) ();
请注意,为了删除分配的元素,您必须手动调用析构函数。您会发现这个示例很有用。
这是根据 intro.object 明确定义的。标准甚至有一个与此类似的示例,其中从
std::malloc
获得的指针被取消引用。
来自intro.object:
[示例]
#include <cstdlib> struct X { int a, b; }; X *make_x() { // The call to std::malloc implicitly creates an object of type X // and its subobjects a and b, and returns a pointer to that X object // (or an object that is pointer-interconvertible ([basic.compound]) with it), // in order to give the subsequent class member access operations // defined behavior. X *p = (X*)std::malloc(sizeof(struct X)); p->a = 1; p->b = 2; return p; }
示例结束]
从 C++20 开始这是合法的。在此之前,这在技术上是非法的,但无论如何在实践中是有效的。
这通常是 UB,因为在对象的生命周期开始之前访问该对象,但 C++20 添加了
[intro.object]/11
:
某些操作被描述为“在指定的存储区域内隐式创建对象”。 对于指定为隐式创建对象的每个操作,该操作会在其指定的存储区域中隐式创建并启动零个或多个隐式生命周期类型 ([basic.types.general]) 的对象的生命周期,如果这样做会导致程序已定义行为。 如果没有这样的对象集可以给程序提供定义的行为,则程序的行为是未定义的。 如果多个这样的对象集将给予程序定义的行为,则未指定创建哪个这样的对象集。
[注4: 此类操作不会启动本身不属于隐式生命周期类型的对象的子对象的生命周期。 — 尾注]
malloc()
因此而受到祝福
[c.malloc]/4
:这些函数在返回的存储区域中隐式创建对象([intro.object]),并返回指向合适的已创建对象的指针。 对于 calloc 和 realloc,对象分别在存储清零或复制之前创建。
(注意“返回一个指向合适的已创建对象的指针”,否则需要
std::launder()
。)
这适用于“隐式生命周期类型”: ...标量类型、隐式生命周期类类型 ([class.prop])、数组类型和这些类型的 cv 限定版本统称为隐式生命周期类型。S 类是隐式生命周期类,如果
(9.1) — 它是一个聚合,其析构函数不是用户提供的或
(9.2) — 它至少有一个简单的合格构造函数和一个简单的、未删除的析构函数。
(9.1) 听起来很混乱,直到您阅读此答案第一个引用中的“注释 4”。