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

时间:2013-05-04 19:14:03

标签: c++ new-operator delete-operator

上一个标题:"我必须更换全局运算符new和delete以更改第三方代码中的内存分配策略吗?"

短篇小说: 我们需要在不改变源代码的情况下替换第三方库中的内存分配技术。

长篇故事:

考虑使用大量动态分配的内存绑定应用程序(可能是几乎所有可用的系统内存)。我们使用专门的分配器,并在任何地方使用它们(shared_ptr' s,容器等)。我们对应用程序中分配的每个字节内存都有完全的控制和功能。

此外,我们需要链接到第三方帮助程序库。那个讨厌的家伙以某种标准方式进行分配,使用默认运算符newnew[]deletedelete[]malloc或其他非标准运算符(让& #39;概括并说我们不知道这个库如何管理它的堆分配。

如果这个帮助程序库进行足够大的分配,我们可能会出现硬盘抖动,内存碎片和对齐问题,内存不足bad_alloc以及各种问题。

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

首次尝试:

我们从来没有过这样的邪恶" hacks"在发布之前构建。使用覆盖运算符new的第一个测试工作正常,但是:

  • 我们不知道将来会有什么等待我们(这很糟糕)
  • 我们的用户(甚至我们的分配器)现在必须按照我们的方式进行分配

问题:

  1. 有没有办法挂钩这些分配而不会重载全局运算符? (本地lib-hook挂钩?)
  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. 如果该库是动态的,会有什么不同吗?

  5. 编辑#1

    如果可能,优选跨平台解决方案(看起来不太可能)。如果没有,我们的主要平台:

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

    编辑#2

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

3 个答案:

答案 0 :(得分:21)

呃,我的同情。这将在很大程度上取决于你的编译器,你的libc等。过去对我们有不同程度“工作”的一些橡胶遇到道路策略(/我支持downvotes)是:

  • 您建议的operator new / operator delete重载 - 尽管请注意,有些编译器对于没有throw()规范很挑剔,有些人真的想要它们,有些想要它们是新的但不是删除等(对于我们现在正在处理的所有4个以上平台,我有一个特定于平台的巨型#if / #elif块。
  • 另外值得注意的是:您通常可以忽略展示位置版本,但不会分配。
  • 请注意__malloc_hook and friends - 请注意这些已被弃用并且具有线程竞争条件 - 但它们很好,新的/删除往往是以malloc实现的(但并非总是如此) )。
  • 提供替换malloccallocreallocfree并以正确的顺序获取链接器参数,以便进行覆盖(这就是gcc)这些天推荐,虽然我遇到过无法做到的情况,我不得不使用已弃用的__malloc_hook) - 再次,newdelete 倾向于以这些方式实施,但并非总是如此。
  • 避免在“我们的代码”中使用所有标准分配方法(operator newmalloc等)并使用自定义函数 - 现有代码库不是很容易。
  • 跟踪图书馆作者并提供野蛮人殴打礼貌请求或修补程序来更改他们的库以允许您指定不同的分配器(它可能比自己这样做更快) - 我认为这导致了我所写的任何库的“客户端总是指定分配器或进行分配”的基本规则。

请注意,就标准所说的应该发生的情况而言, 不是的答案,只是我的经验。我曾经使用过多个错误/破坏的编译器和libc实现,所以YMMV。我也很擅长使用相当“密封的系统”,而不是担心任何特定应用程序的可移植性。

关于动态图书馆:我目前在这方面有点紧张;我们的“app”作为动态.so加载,如果它们不是来自我们,我们必须非常小心地将任何delete / free请求传递回默认分配器。目前的解决方案只是封锁我们对特定区域的分配:如果我们从该地址范围内获得删除/免费,我们将调度到我们的处理程序,否则返回默认值...我甚至玩弄过(恐怖) )检查调用者地址以查看它是否在我们的地址空间中的想法。 (但是,随着这种黑客攻击,繁荣的可能性会增加。)

这可能是一个有用的策略,即使您是流程主管并且您正在使用外部库:标记或限制或以其他方式识别您自己的分配(甚至可以保留您知道的分配列表) ),然后传递任何未知数。然而,所有这些都有丑陋的副作用和限制。

(期待其他答案!)

答案 1 :(得分:2)

无法修改图书馆的源代码 - 或者更好的是,能够影响图书馆的作者修改它 - 我说你运气不好。< / p>

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

可能有效的策略是与图书馆供应商合作并进行一些修改。修改(从您的结束)将相当于能够通过指定它使用的分配器来初始化库。对于库来说,工作可能很重要(必须触摸所有动态分配内存的函数,使用标准容器等)但不是难以处理 - 在代码中使用提供的分配器(或合理的默认值)。

不幸的是,这与你不修改库的要求不一致 - 我对满足这一点的可能性持怀疑态度,特别是在你已经概述的限制内(内存口渴,托管在windows / linux上等)。

答案 2 :(得分:0)

不能在该类库中进行分配,但是您可以使用placement new来从该第三方库分配类,即您可以分配内存并在分配的内存上调用这些类的构造函数。所以这样即使该类有自己的新运算符,它不会被调用.Howvwer,在类操作内部内存分配到未暴露的内部类或基元将使用第三方库的分配方案完成;除非第三方库允许您指定像stl容器这样的分配器

,否则无法更改