如何正确释放placement new分配的内存?

发布于 2024-12-27 18:21:17 字数 725 浏览 0 评论 0原文

我读过一些文章,当您使用 placement new 时,您必须手动调用析构函数。

考虑下面的代码:

   // Allocate memory ourself
char* pMemory = new char[ sizeof(MyClass)];

// Construct the object ourself
MyClass* pMyClass = new( pMemory ) MyClass();

// The destruction of object is our duty.
pMyClass->~MyClass();

据我所知,operator delete 通常会调用析构函数,然后释放内存,对吗?那么我们为什么不使用 delete 来代替呢?

delete pMyClass;  //what's wrong with that?

在第一种情况下,在我们像这样调用析构函数后,我们被迫将 pMyClass 设置为 nullptr:

pMyClass->~MyClass();
pMyClass = nullptr;  // is that correct?

但是析构函数没有释放内存,对吧? 那么这会是内存泄漏吗?

我很困惑,你能解释一下吗?

I've been reading somewere that when you use placement new then you have to call the destructor manually.

Consider the folowing code:

   // Allocate memory ourself
char* pMemory = new char[ sizeof(MyClass)];

// Construct the object ourself
MyClass* pMyClass = new( pMemory ) MyClass();

// The destruction of object is our duty.
pMyClass->~MyClass();

As far as I know operator delete normally calls the destructor and then deallocates the memory, right? So why don't we use delete instead?

delete pMyClass;  //what's wrong with that?

in the first case we are forced to set pMyClass to nullptr after we call destructor like this:

pMyClass->~MyClass();
pMyClass = nullptr;  // is that correct?

BUT the destructor did NOT deallocate memory, right?
So would that be a memory leak?

I'm confused, can you explain that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

心房敞 2025-01-03 18:21:17

使用 new 表达式可以做两件事,它调用分配内存的函数operator new,然后使用placement new 在该内存中创建对象。 delete 表达式调用对象的析构函数,然后调用operator delete。是的,名字很混乱。

//normal version                   calls these two functions
MyClass* pMemory = new MyClass;    void* pMemory = operator new(sizeof(MyClass));
                                   MyClass* pMyClass = new( pMemory ) MyClass();
//normal version                   calls these two functions
delete pMemory;                    pMyClass->~MyClass();
                                   operator delete(pMemory);

由于在您的情况下,您手动使用了放置新,因此您还需要手动调用析构函数。由于您手动分配了内存,因此需要手动释放它。

但是,placement new 也被设计为与内部缓冲区(以及其他场景)一起使用,其中缓冲区使用operator new分配,这就是为什么您不应该这样做对它们调用operator delete

#include <type_traits>

struct buffer_struct {
    std::aligned_storage_t<sizeof(MyClass), alignof(MyClass)> buffer;
};
int main() {
    buffer_struct a;
    MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a
    //stuff
    pMyClass->~MyClass(); //can't use delete, because there's no `new`.
    return 0;
}

buffer_struct 类的目的是以任何方式创建和销毁存储,而 main 则负责 MyClass 的构造/销毁,请注意两者是如何(几乎*)完全分开的。

*我们必须确保存储空间足够大

Using the new expression does two things, it calls the function operator new which allocates memory, and then it uses placement new, to create the object in that memory. The delete expression calls the object's destructor, and then calls operator delete. Yeah, the names are confusing.

//normal version                   calls these two functions
MyClass* pMemory = new MyClass;    void* pMemory = operator new(sizeof(MyClass));
                                   MyClass* pMyClass = new( pMemory ) MyClass();
//normal version                   calls these two functions
delete pMemory;                    pMyClass->~MyClass();
                                   operator delete(pMemory);

Since in your case, you used placement new manually, you also need to call the destructor manually. Since you allocated the memory manually, you need to release it manually.

However, placement new is designed to work with internal buffers as well (and other scenarios), where the buffers were not allocated with operator new, which is why you shouldn't call operator delete on them.

#include <type_traits>

struct buffer_struct {
    std::aligned_storage_t<sizeof(MyClass), alignof(MyClass)> buffer;
};
int main() {
    buffer_struct a;
    MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a
    //stuff
    pMyClass->~MyClass(); //can't use delete, because there's no `new`.
    return 0;
}

The purpose of the buffer_struct class is to create and destroy the storage in whatever way, while main takes care of the construction/destruction of MyClass, note how the two are (almost*) completely separate from each other.

*we have to be sure the storage has to be big enough

没有你我更好 2025-01-03 18:21:17

这是错误的原因之一是:

delete pMyClass;

您必须使用 delete[] 删除 pMemory,因为它是一个数组:

delete[] pMemory;

您不能同时执行上述两项操作。

同样,您可能会问为什么不能使用 malloc() 分配内存,放置 new 来构造对象,然后使用 delete 删除并释放内存。原因是您必须匹配 malloc()free(),而不是 malloc()delete

在现实世界中,放置新的和显式的析构函数调用几乎从未被使用过。它们可能由标准库实现内部使用(或用于注释中指出的其他系统级编程),但普通程序员不使用它们。在多年的 C++ 开发生涯中,我从未在生产代码中使用过这样的技巧。

One reason this is wrong:

delete pMyClass;

is that you must delete pMemory with delete[] since it is an array:

delete[] pMemory;

You can't do both of the above.

Similarly, you might ask why you can't use malloc() to allocate memory, placement new to construct an object, and then delete to delete and free the memory. The reason is that you must match malloc() and free(), not malloc() and delete.

In the real world, placement new and explicit destructor calls are almost never used. They might be used internally by the Standard Library implementation (or for other systems-level programming as noted in the comments), but normal programmers don't use them. I have never used such tricks for production code in many years of doing C++.

像极了他 2025-01-03 18:21:17

您需要区分delete 运算符和operator delete。特别是,如果您使用placement new,则显式调用析构函数,然后调用operator delete(而不是delete操作符)来释放内存,即

X *x = static_cast<X*>(::operator new(sizeof(X)));
new(x) X;
x->~X();
::operator delete(x);

请注意这使用operator delete,它比delete运算符级别更低,并且不担心析构函数(它本质上有点像free) 。将此与删除运算符进行比较,后者在内部执行的操作相当于调用析构函数并调用删除运算符。

值得注意的是,您不必使用 ::operator new::operator delete 来分配和释放缓冲区 - 就放置 new 而言,它缓冲区如何产生/被破坏并不重要。要点是将内存分配和对象生命周期的关注点分开。

顺便说一句,这种情况的一个可能的应用是在游戏中,您可能希望预先分配一大块内存,以便仔细管理内存使用情况。然后,您可以在已经获得的内存中构造对象。

另一种可能的用途是优化的小型、固定大小的对象分配器。

You need to distinguish between the delete operator and operator delete. In particular, if you're using placement new, you explicitly invoke the destructor and then call operator delete (and not the delete operator) to release the memory, i.e.

X *x = static_cast<X*>(::operator new(sizeof(X)));
new(x) X;
x->~X();
::operator delete(x);

Note that this uses operator delete, which is lower-level than the delete operator and doesn't worry about destructors (it's essentially a bit like free). Compare this to the delete operator, which internally does the equivalent of invoking the destructor and calling operator delete.

It's worth noting that you don't have to use ::operator new and ::operator delete to allocate and deallocate your buffer - as far as placement new is concerned, it doesn't matter how the buffer comes into being / gets destroyed. The main point is to separate the concerns of memory allocation and object lifetime.

Incidentally, a possible application of this would be in something like a game, where you might want to allocate a large block of memory up-front in order to carefully manage your memory usage. You'd then construct objects in the memory you've already acquired.

Another possible use would be in an optimized small, fixed-size object allocator.

顾冷 2025-01-03 18:21:17

如果您想象在一个内存块中构造几个 MyClass对象,可能会更容易理解。

在这种情况下,它会像这样:

  1. 使用 new char[10*sizeof(MyClass)] 或 malloc(10*sizeof(MyClass)) 分配一个巨大的内存块
  2. 使用placement new 在该内存中构造十个 MyClass 对象。
  3. 做点什么。
  4. 调用每个对象的析构函数
  5. 使用delete[]或free()释放大块内存。

如果您正在编写编译器或操作系统等,那么您可能会这样做。

在这种情况下,我希望您清楚为什么需要单独的“析构函数”和“删除”步骤,因为您没有理由调用删除。但是,您应该按照通​​常的方式释放内存(释放、删除、对巨大的静态数组不执行任何操作、如果数组是另一个对象的一部分则正常退出等),并且如果你不这样做,它就会被泄露。

另请注意,正如 Greg 所说,在这种情况下,您不能使用 delete,因为您使用 new[] 分配了数组,因此您需要使用 delete[]。

另请注意,您需要假设您没有覆盖 MyClass 的删除,否则它将执行完全不同的操作,这几乎肯定与“new”不兼容。

所以我认为你不太可能像你所描述的那样调用“删除”,但是它能起作用吗?我认为这基本上与“我有两个不相关的类型没有析构函数。我可以新建一个指向一种类型的指针,然后通过指向另一种类型的指针删除该内存吗?”是同一个问题。换句话说,“我的编译器是否有一个包含所有已分配内容的大列表,或者它可以针对不同类型执行不同的操作”。

恐怕我不确定。阅读规范它说:

5.3.5 ...如果[删除运算符]操作数的静态类型与其动态类型不同,则静态类型应为基类
操作数的动态类型和静态类型的类应具有
虚拟析构函数或行为未定义。

我认为这意味着“如果你使用两个不相关的类型,它就不起作用(通过虚拟析构函数多态地删除一个类对象是可以的)”。

所以不,不要这样做。我怀疑它在实践中可能经常起作用,如果编译器确实只查看地址而不是类型(并且这两种类型都不是多重继承类,这会破坏地址),但不要尝试它。

It's probably easier to understand if you imagine constructing several MyClass objects within one block of memory.

In that case, it would go something like:

  1. Allocate a giant block of memory using new char[10*sizeof(MyClass)] or malloc(10*sizeof(MyClass))
  2. Use placement new to construct ten MyClass objects within that memory.
  3. Do something.
  4. Call the destructor of each of your objects
  5. Deallocate the big block of memory using delete[] or free().

This is the sort of thing you might do if you're writing a compiler, or an OS, etc.

In this case, I hope it's clear why you need separate "destructor" and "delete" steps, because there's no reason you will call delete. However, you should deallocate the memory however you would normally do it (free, delete, do nothing for a giant static array, exit normally if the array is part of another object, etc, etc), and if you don't it'll be leaked.

Also note as Greg said, in this case, you can't use delete, because you allocated the array with new[] so you'd need to use delete[].

Also note that you need to assume you haven't overridden delete for MyClass, else it will do something totally different which is almost certainly incompatible with "new".

So I think you're unlikley to want to call "delete" as you describe, but could it ever work? I think this is basically the same question as "I have two unrelated types that don't have destructors. Can I new a pointer to one type, then delete that memory through a pointer to another type?" In other words, "does my compiler have one big list of all allocated stuff, or can it do different things for different types".

I'm afraid I'm not sure. Reading the spec it says:

5.3.5 ... If the static type of the operand [of the delete operator] is different from its dynamic type, the static type shall be a base
class of the operand's dynamic type and the static type shall have a
virtual destructor or the behaviour is undefined.

I think that means "If you use two unrelated types, it doesn't work (it's ok to delete a class object polymorphically through a virtual destructor)."

So no, don't do that. I suspect it may often work in practice, if the compiler does look solely at the address and not the type (and neither type is a multiple-inheritance class, which would mangle the address), but don't try it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文