如何控制第三方库代码中的内存分配策略?

以前的标题:“我必须更换全球运营商的新增和删除来更改第三方代码中的内存分配策略吗?

小故事:我们需要更换第三方库中的内存分配技术,而不更改其源代码。

很长的故事:

考虑一下内存限制的应用程序,它可以实现巨大的dynamic分配(也许几乎所有可用的系统内存)。 我们使用专门的分配器,并在任何地方使用它们( shared_ptr ,容器等)。 我们对应用程序中分配的每个单字节内存都有完全的控制权。

另外,我们需要链接到第三方的帮助程序库 。 这个讨厌的人以某种标准的方式进行分配,使用默认的运算符newnew[]delete delete[]malloc或其他非标准的东西(让我们概括并说我们不知道这个库如何pipe理它的堆分配)。

如果这个帮助程序库进行足够大的分配,我们可以得到HDD抖动,内存碎片和alignment问题,内存不足bad_alloc以及各种问题。

我们不能(或不想)更改库源代码。

第一次尝试:

我们从来没有在发布版本中有过这样的邪恶“黑客”。 首先testing覆盖运营商的new作品罚款,除了:

  • 我们不知道将来会有什么等待我们(这太糟糕了)
  • 我们的用户(甚至是我们的分配者)现在必须以与我们相同的方式分配

问题:

  1. 有没有办法钩住这些分配而不会使全局运算符重载? (本地lib只钩子?)
  2. …如果我们不知道它到底用了什么: mallocnew
  3. 这个签名清单是否完整? (而且没有其他的东西我们必须执行):

     void* operator new (std::size_t size) throw (std::bad_alloc); void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw(); void* operator new (std::size_t size, void* ptr) throw(); void* operator new[] (std::size_t size) throw (std::bad_alloc); void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) throw(); void* operator new[] (std::size_t size, void* ptr) throw(); void operator delete (void* ptr) throw(); void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) throw(); void operator delete (void* ptr, void* voidptr2) throw(); void operator delete[] (void* ptr) throw(); void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) throw(); void operator delete[] (void* ptr, void* voidptr2) throw(); 
  4. 如果图书馆是dynamic的,有什么不同?

编辑#1

如果可能,跨平台的解决scheme是可取的(看起来不太可能)。 如果没有,我们的主要平台:

  • Windows x86 / x64(msvc 10)
  • Linux x86 / x64(gcc 4.6)

编辑#2

差不多2年过去了,很less有操作系统和编译器的版本已经发展,所以我很好奇这个领域是否有新的东西和未开发的东西? 任何标准提案? OS-具体情况如何? 黑客? 你今天如何编写记忆渴求的应用程序? 请分享你的经验。

呃,我的同情。 这将取决于你的编译器,libc等等。过去,我们在某种程度上“为我们工作”的橡胶 – 路接策略(/ me支撑downvotes)是:

  • 你提到的operator new / operator delete超载 – 尽pipe注意到有些编译器对于没有throw()规格说是挑剔的,有些真的需要它们,有些希望它们是新的但不是用于删除等等(我有一个巨大的平台特定的现在我们正在处理的所有4+平台的#if / #elif块)。
  • 另外值得注意的是:你通常可以忽略放置版本,他们不分配。
  • 看看__malloc_hook和朋友 – 注意,这些已被弃用,并有线程竞争条件 – 但他们很好,新/删除倾向于malloc (但不是总是)方面实施。
  • 提供一个replace的malloccallocreallocfree并以正确的顺序获取链接器参数,以便覆盖发生(这是gcc现在推荐的,虽然我已经遇到了无法做到的情况,不得不使用已弃用的__malloc_hook ) – 再次, newdelete 倾向于在这些方面实施,但并非总是如此。
  • 在“我们的代码”中避免使用所有的标准分配方法( operator newmalloc等)并使用自定义函数 – 对于现有的代码库不是很容易。
  • 跟踪图书馆的作者,并提供一个野蛮的殴打礼貌的请求或补丁来改变他们的库,让你指定一个不同的分配器(这可能会比自己做这个更快) – 我认为这导致了“客户端总是指定分配器或执行分配“与我写的任何库。

请注意,这不是标准说应该发生什么,只是我的经验。 在过去,我已经使用了多个bug /破解的编译器和libc实现,所以YMMV。 我也有相当“密封的系统”的工作的豪华,而不是所有的担心任何具体应用的可移植性。

关于dynamic图书馆:我自己目前在这方面有点勉强, 我们的“应用程序”被加载为一个dynamic的.so ,我们必须非常小心地将任何delete / free请求传递回默认的分配器,如果他们不是来自我们。 目前的解决scheme是把我们的分配放在一个特定的区域:如果我们在这个地址范围内得到一个删除/释放,我们派遣到我们的处理程序,否则回到默认的…我甚至玩弄(恐怖)检查来电者地址的想法,看看它是否在我们的地址空间。 (尽pipe如此,黑客攻势的可能性也在增加。)

这可能是一个有用的策略,即使你是stream程负责人,而且你正在使用一个外部库:标记或限制或以某种方式识别你自己的分配(甚至可以保留你知道的分配列表),以及然后传递任何未知数。 尽pipe如此,所有这些都有难看的副作用和限制。

(期待其他答案!)

如果不能修改图书馆的源代码,或者更好的是能够影响图书馆的作者来修改它,我会说你不走运。

图书馆可能会做一些事情(甚至是无意的),使其免于使用任何策略 – 或者在最糟糕的情况下,会导致图书馆使用不稳定或者使程序变得不稳定。 比如使用自己的自定义分配器,提供自己版本的全局operator new()operator delete() ,覆盖单个类中的运算符等。

一个可能工作的策略是与图书馆供应商合作并做一些修改。 修改(从你的最后)将等于能够通过指定它使用的分配器来初始化库。 对于这个库来说,这个努力是潜在的重要的(不得不去触及dynamic分配内存的所有函数,使用标准容器等),但不是棘手的 – 在代码中使用提供的分配器(或合理的默认值)。

不幸的是,这是不符合你的要求,不要修改图书馆 – 我怀疑满足这一点,尤其是在你所概述的限制(内存渴望,托pipe在Windows / Linux等)的机会。

无法完成在该类库中进行的分配,但是可以使用placement new从第三方库中分配类,也就是说,您可以分配内存,并在分配的内存中调用这些类的构造函数。因此,即使类它自己的新操作符不会被调用。在类操作中,内存分配到未暴露的内部类或原语将使用第三方库的分配scheme完成。 不能改变,除非第三方库允许你指定一个分配器像stl容器