ODA内存分配简要概述
ODA不断寻求推进和改进其技术的方法。其中一个例子就是它如何处理内存分配。
过去,ODA软件根据在TD_Alloc模块中定义并从该模块导出到每个ODA项目的重载new/delete全局运算符来分配内存。原因很简单:为了提供捕获内存泄漏和为ODA成员编写自定义内存管理的能力。然而,这种方式是不正确的,原因详述如下。
-
不符合C++标准。C++17草案指出:
3.7.4.2节,第65页
“库为全局分配和解除分配函数提供了默认定义。一些全局分配和解除分配函数是可替换的(18.6.1)。C++程序应最多提供一个可替换的分配或解除分配函数的定义。任何此类函数定义都将替换库中提供的默认版本(17.6.4.6)。”
17.6.4.6节,第460页
“C++程序可以为头文件中声明的十二个动态内存分配函数签名中的任何一个提供定义(3.7.4,18.6):[...] 程序提供的定义将取代实现提供的默认版本(18.6)。”
标准将“程序”定义为:
“一个程序由一个或多个链接在一起的翻译单元(第2章)组成。”
考虑到这一点,每个DLL都是一个“程序”,因此每个DLL都应该有自己内部版本的内存分配运算符。
- ODA成员报告了诸如“随机崩溃、分配和删除不匹配”等问题。例如,请参阅我们的论坛(需要登录)。此外,许多成员在使用第三方内存泄漏检测器与ODA模块时遇到了问题。
- 当定义了多个相同全局分配或解除分配运算符版本时加载ODA库是未定义行为,并可能导致其他问题。
- 在内部,我们定期在使用自己的内存泄漏检测器时发现问题。由于分配运算符是全局重载的,因此ODA项目中存在强大的依赖链接顺序要求。TD_Alloc应在模块依赖项中的任何其他库之前,以管理重载标准分配运算符。OdaProjectGenerator中甚至有一个特殊脚本来检查链接顺序。此外,在添加新大小的解除分配运算符(C++ 14标准)时也存在一个问题,因为简单地将“export”调用约定添加到这些运算符中不起作用,这就是为什么需要使用.def文件的先前解决方法。
重构内存分配
在内存分配重构期间,有几个目标(除了消除上一段中描述的问题):
- 保留我们现有的功能。
- 使更改对ODA开发人员透明:避免长时间重写现有模块的要求,例如单独为每个模块添加分配运算符。
- 使ODA开发人员和ODA成员能够轻松地使用我们的分配器编写进一步的模块/扩展。
因此,进行了以下更改:
- 新的分配运算符在 OdAllocOp.h 文件中定义,并通过宏 ODRX_DEFINE_DYNAMIC_MODULE 添加到 oda_tx 和 oda_txv 模块中。因此,当 ODA 开发人员和 ODA 成员为基于 ODA 的应用程序创建新扩展时,完全不需要记住这些运算符(但提供 odrxAlloc/odrxFree 函数的 TD_Alloc 模块仍然必须链接到该模块)。
- 添加了一种新的模块类型 oda_tx_component,用于支持 DLL 模块,它也具有 ODRX_DEFINE_DYNAMIC_MODULE 宏(主要用于内部使用)。
- 对于其他 DLL 模块 (oda_components),运算符已添加到 OdAllocOp.cpp 文件中,该文件包含 OdAllocOp.h 中的相同内容。在生成阶段,CMake 解析项目并将此文件添加到依赖项中包含 TD_Alloc 的组件中。
- ODA 库模块现在可以独立于 TD_Alloc(如果未直接从其中调用 odrxAlloc/odrxFree)。
- 以上所有操作都是针对动态配置完成的。但对于可执行模块 (oda_executables),CMake 现在为动态和静态配置都添加了 OdAllocOp.cpp。
现在的工作方式
很简单:根据 C++ 标准,每个 ODA 模块都独立定义了 new/delete 运算符。TD_Alloc 仍然为内存泄漏和内存管理目的提供 odrxAlloc/odrxFree 函数,但它不再覆盖全局 new/delete 运算符。
现在要创建扩展模块,不需要任何以前不需要的东西。此外,如果 TD_Alloc 未添加到模块依赖项中,将会出现构建错误。
对于其他模块,需要将 TD_Alloc 添加到模块依赖项中,如果未使用 ODAProjectGenerator,则需要将 Extensions/alloc 中的 OdAllocOp.cpp 添加到项目中(如果使用,则会自动添加该文件)。