上一个标题:“我必须替换全局运算符 new 和 delete 以更改第三方代码中的内存分配策略吗?”
短篇小说: 我们需要替换第三方库中的内存分配技术而不改变其源代码。
长篇大论:
考虑进行大量动态分配(也许几乎所有可用系统内存)的内存绑定(bind)应用程序。我们使用专门的分配器,并在任何地方使用它们(shared_ptr
's、容器等)。我们对应用程序中分配的每一个内存字节拥有完全的控制权和权力。
另外,我们需要链接到一个第三方帮助库。那个讨厌的家伙以某种标准方式进行分配,使用默认运算符 new
、new[]
、delete
和 delete[]
或 malloc
或其他非标准的东西(让我们概括并说我们不知道这个库如何管理它的堆分配)。
如果这个帮助程序库进行了足够大的分配,我们可能会遇到 HDD 抖动、内存碎片和对齐问题、内存不足 bad_alloc
和各种问题。
我们不能(或不想)更改库源代码。
第一次尝试:
我们以前从未在发布版本中遇到过如此邪恶的“黑客行为”。使用覆盖运算符 new
进行的第一次测试工作正常,除了:
- 我们不知道 future 会遇到什么问题(这太可怕了)
- 我们的用户(甚至我们的分配器)现在必须以与我们相同的方式进行分配
问题:
- 有没有办法在不重载全局运算符的情况下 Hook 这些分配? (仅限本地库的 Hook ?)
- ...如果我们不知道它到底使用什么:
malloc
或new
? 这个签名列表是否完整? (我们没有其他必须实现的东西):
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();
如果该库是动态的,会有什么不同?
编辑#1
如果可能,最好使用跨平台解决方案(看起来不太可能)。如果没有,我们的主要平台:
- Windows x86/x64 (msvc 10)
- Linux x86/x64 (gcc 4.6)
编辑#2
将近 2 年过去了,很少有操作系统和编译器版本进化,所以我很好奇这方面是否有一些新的和未开发的东西?有什么标准建议吗?操作系统细节?黑客?您今天如何编写需要大量内存的应用程序?请分享您的经验。
最佳答案
呃,我的同情。这将在很大程度上取决于您的编译器、您的 libc 等。过去对我们来说在不同程度上“起作用”的一些橡胶与道路策略(/me 大括号表示反对票)是:
- 您建议的
operator new
/operator delete
重载 - 尽管请注意,有些编译器对没有throw()
规范很挑剔,有些人真的想要它们,有些人想要它们用于新的但不用于删除等(我有一个巨大的特定于平台的#if
/#elif
block 用于所有 4+我们现在正在开发的平台)。 - 另外值得注意的是:您通常可以忽略展示位置版本,它们不会分配。
- 看
__malloc_hook
and friends -- 请注意,这些已被弃用并且有线程竞争条件 -- 但它们很好,因为 new/delete 往往是根据malloc
实现的(但并非总是如此)。 - 提供替换
malloc
、calloc
、realloc
和free
并让您的链接器参数正确顺序以便覆盖发生(这是 gcc 这些天推荐的,尽管我遇到过不可能做到的情况,我不得不使用已弃用的__malloc_hook
)——再次,new
和delete
倾向于根据这些来实现,但并非总是如此。 - 在“我们的代码”中避免使用所有标准分配方法(
operator new
、malloc
等)并改用自定义函数——对于现有的代码库来说并不容易。 - 跟踪库作者并提供
savage beat礼貌请求或补丁以更改他们的库以允许您指定不同的分配器(这可能比自己执行此操作更快)——我认为这导致了我编写的任何库的“客户端始终指定分配器或进行分配”的基本规则。
请注意,这不是标准所说的应该发生的答案,只是我的经验。过去,我使用过不少有问题/损坏的编译器和 libc 实现,所以 YMMV。我也有幸在相当“密封的系统”上工作,而不必担心任何特定应用程序的可移植性。
关于动态库:我自己目前在这方面有点困难;我们的“应用程序”作为动态 .so
加载,我们必须非常小心地将任何 delete
/free
请求传递回默认值如果它们不是来 self 们的分配器。当前的解决方案是封锁我们对特定区域的分配:如果我们从该地址范围内获得删除/释放,我们将调度到我们的处理程序,否则返回默认值......我什至玩弄过(恐怖) 检查调用者地址以查看它是否在我们的地址空间中的想法。 (不过,这种黑客攻击会增加繁荣的可能性。)
即使您是流程负责人并且您正在使用外部库,这也可能是一个有用的策略:标记或限制或以其他方式以某种方式识别您自己的分配(甚至保留您知道的分配列表) ),然后传递任何未知数。不过,所有这些都有丑陋的副作用和局限性。
(期待其他答案!)
关于c++ - 如何控制第三方库代码中的内存分配策略?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16377991/