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

Iva*_*rop 37 c++ new-operator delete-operator

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

简短的故事: 我们需要在不改变源代码的情况下替换第三方库中的内存分配技术.

很长的故事:

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

此外,我们需要链接第三方帮助程序库.那个讨厌的家伙使得分配在一些标准的方法,使用默认的运营商new,new[],deletedelete[]malloc或别的东西非标准(让我们一概而论说我们不知道这个库如何管理它的堆分配).

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

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

第一次尝试:

我们之前从未在发布版本中遇到过这种不圣洁的"hack".使用覆盖运算符的第一次测试new工作正常,但以下情况除外:

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

问题:

  1. 有没有办法挂钩这些分配而不会重载全局运营商?(本地lib-hook挂钩?)
  2. ......如果我们不知道它究竟用于什么:malloc或者new
  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();
    
    Run Code Online (Sandbox Code Playgroud)
  4. 如果该库是动态的,那会有什么不同吗?

编辑#1

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

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

编辑#2

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

lea*_*der 21

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

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

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

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

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

(期待其他答案!)

  • 我对最后一个要点非常赞同.库不应该假设如何分配内存.(我希望C++提供更强大的工具来管理它).+1 (4认同)