内存领域的异常安全

发布于 2024-12-22 11:21:00 字数 829 浏览 1 评论 0原文

我正在编写一个简单的内存分配器,并面临异常安全的小问题。这种情况是当您分配一个对象时,该对象本身会调用分配器。内存池的目标是一次分配一堆对象,然后在池销毁时将它们全部删除。

{
    MemoryArena m;
    std::string* ptr = m.Allocate<std::string>();
    // use ptr whatever
    // Cleaned up when pool is destroyed
}

但当多次使用时,这会变得相当令人讨厌。如果内部分配被清理,那么它可以在以后使用——这不是一个坏的假设,因为池的定义是在它的生命周期结束之前永远不会删除对象。考虑:

struct X {
    X(MemoryArena* ptr, std::string*& ref) {
        ref = ptr->Allocate<std::string>();
        throw std::runtime_error("hai");
    }
};
MemoryArena m;
std::string* ptr;
m.Allocate<X>(&m, ptr);
// ptr is invalid- even though it came from the arena 
// which hasn't yet been destroyed

但是如果内部分配没有被清理,外部分配也无法被清理,因为内存区域像在硬件堆栈上一样线性分配它们,所以我泄漏了内存。因此,要么我通过提前销毁对象来违反我的语义,要么我泄漏内存。

对于如何解决这个问题有什么建议吗?

I'm writing a simple memory arena allocator and facing a small problem with exception safety. The situation is when you allocate an object which itself calls the allocator. The objective of the memory pool is to allocate a bunch of objects at one time, and then delete them all when the pool is destroyed.

{
    MemoryArena m;
    std::string* ptr = m.Allocate<std::string>();
    // use ptr whatever
    // Cleaned up when pool is destroyed
}

But this gets rather nasty when it's used multiple times. If the inner allocation is cleaned up, then it could be used afterwards- not a bad assumption, since it's the definition of the pool to never delete objects until it's lifetime is over. consider:

struct X {
    X(MemoryArena* ptr, std::string*& ref) {
        ref = ptr->Allocate<std::string>();
        throw std::runtime_error("hai");
    }
};
MemoryArena m;
std::string* ptr;
m.Allocate<X>(&m, ptr);
// ptr is invalid- even though it came from the arena 
// which hasn't yet been destroyed

But if the inner allocation isn't cleaned up, the outer allocation also can't be cleaned up, because the memory arena allocates them linearly like on a hardware stack, so I leak memory. So either I violate my semantics by destroying an object early, or I leak memory.

Any suggestions for how to resolve this problem?

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

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

发布评论

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

评论(2

月棠 2024-12-29 11:21:00

也许这只是适用的示例代码,但我认为用户不应该假设当 X 的构造函数抛出时 ptr 是有效的。它也可能在 ref 被分配之前抛出。

所以我想说,如果可以的话,清理内部对象(即,如果竞技场的前面当前位于内部对象的末尾)。好吧,来自竞技场的分配变得无效,这是不正常的,但无论如何,这是一个永远不应该进入现实世界的分配。

也许您可以使用“软”分配的概念来明确这一点。它不能保证永远存在,因为虽然仍然“软”,但它可以被释放回竞技场。那么 X 的构造函数会执行类似的操作:

SoftPtr<std::string> tmp(ptr->SoftAllocate<std::string>());
stuff_that_might_throw(); 
ref = tmp.release();

在没有首先调用 release 的情况下执行 SoftPtr 的析构函数意味着没有公开对该对象的引用。它调用 MemoryArena 的函数,该函数执行以下操作:

  • 析构对象
  • 的最新分配
  • 检查这是否是来自 arena
    • 如果是,则从竞技场的当前位置指针中减去大小
    • 如果没有,则不执行任何其他操作(内存泄漏)

因此,只要以相反的顺序完成,就可以“撤消”任意数量的分配。

Maybe it's just the example code that this applies to, but I don't think the user should assume that ptr is valid when the constructor of X throws. It could just as well have thrown before ref was assigned.

So I'd say clean up the inner object if you can (i.e. if the front of the arena currently lies at the end of the inner object). OK, an allocation from the arena becomes invalid, which isn't normal, but it's an allocation that should never had made it out into the real world anyway.

Perhaps you could make this explicit, with a concept of a "soft" allocation. It's not guaranteed to live forever, because while still "soft" it can be freed back to the arena. Then X's constructor would do something like:

SoftPtr<std::string> tmp(ptr->SoftAllocate<std::string>());
stuff_that_might_throw(); 
ref = tmp.release();

Executing the destructor of SoftPtr without without first calling release implies that no reference to the object has been exposed. It calls a function of MemoryArena that does something like:

  • destruct the object
  • check whether this is the most recent allocation from the arena
    • if so, subtract the size from the arena's current position pointer
    • if not, do nothing else (the memory is leaked)

So any number of allocs can be "backed out", provided it's done in reverse order.

抚笙 2024-12-29 11:21:00

根据内存池的语义,正如您在问题中所说,只有整个池才能被释放,而不能释放单个对象。但你确实想这么做。

一种可能是为分配器配备类似brk的函数来获取和设置下一个分配地址。这为您提供了一个低级机制,您可以使用它来构建您想要的任何内容。

By the very semantics of memory pools, and as you have stated yourself in the question, only the pool as a whole can be freed, not individual objects. Yet you want to do exactly that.

A possibility is to equip the allocator with brk-like functions to get and set the next allocation address. This gives you a low-level mechanism that you can use to built whatever you want on top of it.

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