为什么要对堆栈中的内存使用自定义动态内存分配?

Why use custom dynamic memory allocation over memory from stack?

本文关键字:内存 自定义 动态 分配 为什么 堆栈      更新时间:2023-10-16

上下文:我正在做一个项目,客户需要我们使用自定义动态内存分配,而不是从堆栈中分配对象。请注意,有问题的对象在编译期间具有已知的大小,甚至不需要动态分配。这让我很纳闷,

在哪些上下文中,对象的自定义动态内存分配可能比从堆栈中分配对象更好?(在编译过程中已知大小)


举个例子。如果Dog是一个类,那么他们希望我们做的不仅仅是声明Dog puppy;

Dog* puppy = nullptr; 
custom_alloc(puppy);
new(puppy) Dog(); // the constructor
// do stuff
puppy->~Dog(); // the destructor
custom_free(puppy)

我们不知道真正的custom_alloc函数。为了使程序运行,给定的custom_alloc函数将是malloc的包装器。custom_free将是free的包装

我不喜欢这种方法,并且想知道这何时真正有用,或者他们真正想通过这样做来解决什么。

可能的原因:

堆栈
  1. 大小是有限的;虽然典型的线程库为每个线程的堆栈分配 1-10 MB,但对于预计同时启动数百或数千个线程的应用程序(例如高流量 Web 服务器;Microsoft IIS 过去使用 256 KB 的限制,并且仅在 64 位设置中将其增加到 512 KB)。

  2. 您可能希望在函数返回后保留一个对象(不使用全局变量)。虽然 NRVO 和/或移动语义确实意味着按值返回对象通常相对便宜,但当 NRVO 不适用时,复制单个指针比其他任何东西都便宜。

  3. 审核/跟踪:他们可能希望对特定类型使用自定义函数来跟踪内存分配模式

  4. 持久存储:分配器可能由内存映射文件支持;对于结构化数据,该文件可能兼作长期存储

  5. 性能:已知自定义分配器(例如英特尔的 TBB)在某些情况下会显著缩短运行时间。这更像是使用自定义分配器而不是默认分配器的理由;自定义分配器通常不会击败堆栈存储(除非在非常小众的情况下,可以通过从堆栈中删除大型对象并将它们放在自己的专用存储中来改善内存局部性)。

  6. (可能是一个糟糕的主意)避免异常处理清理开销。如果您的类是 RAII,则必须生成代码以在发生异常时沿各种代码路径清理它们。原始指针不会生成任何此类代码。当然,如果您不采取措施自己执行异常清理,这意味着内存泄漏,但在极少数情况下(例如,当您期望程序完全退出时,并且您希望操作系统处理内存清理时),这可能会提供一个小的"好处"。

  7. 上述组合:他们可能希望能够通过链接不同的运行时库来提供custom_alloc

综上所述,他们这样做的方法非常糟糕;需要手动放置new和析构函数调用是不愉快的(std::unique_ptr/std::shared_ptr可以通过提供自定义删除器函子来帮助您完成这项工作,但即使如此也很丑陋)。通常,如果您需要自定义分配器,则需要为operator new/operator delete定义适当的重载。这样,避免堆栈分配(无论出于何种原因)就不会那么令人不快;您只需将逻辑堆栈分配的变量替换为std::unique_ptrs(通过std::make_unique创建),您的代码仍然相当简单。