使用自定义分配器及其替代项重载基本类型

Overloading base types with a custom allocator, and its alternatives

本文关键字:重载 类型 自定义 分配器      更新时间:2023-10-16

所以,这是一个悬而未决的问题。但是,假设我有一个大型应用程序,该应用程序全局覆盖各种newdelete运算符,以便他们使用自制的jemalloc风格的竞技场和自定义对齐方式。

一切都很好,但我遇到了段错误问题,因为其他基于 C++ 的 DLL 及其依赖项也在不应该使用重载分配器(即 LLVM)时使用重载分配器,使小自定义分配器屈服(缺乏内存和更多压力)。

测试解决方法,我已经将这些全局运算符包装(并移动)到一个类中,并使所有基类都继承自它。嗯,这适用于类,但不适用于基类型。这就是问题所在。


鉴于C++不允许有用的事情,例如每个namespace都有单独的分配器,或者限制每个可执行模块的new运算符,在基本数据类型中模拟它的最佳方法是什么,我不能直接子类化int

显而易见的方法是将它们包装在自定义模板中,但问题是性能。我是否必须模拟第二层下的所有数组和索引操作,以便我可以从不同的地方malloc,而不必更改其余的功能代码?有更好的方法吗?

PS:我也一直在考虑使用带有额外参数的特殊全局new/delete运算符,同时保留标准运算符。从而确保我是(好吧,我的可执行模块是)唯一调用这些全局函数的人。它应该是一个简单的搜索和替换。

嗯,快速更新。我最终为">解决"这个难题所做的是手动检测调用被覆盖的全局分配器的代码是否来自主可执行模块,并有条件地将所有外部new/delete调用重定向到其相应的malloc/free,同时仍然使用自定义竞技场分配器作为我们自己的内部代码。


如何?在做了一些研发之后,我发现这可以通过使用MSVC上内置的_ReturnAddress()和GCC/Clang上的__builtin_extract_return_addr(__builtin_return_address(0))来完成;我可以说到目前为止,它在生产软件中似乎运行良好。

现在,当地址空间中的一些C++代码需要一些内存时,我们可以看到它来自哪里。

但是,我们如何确定该地址是我们流程空间中其他模块的一部分还是我们自己的模块?我们可能需要找出主程序的址和结束地址,在启动时将它们缓存为全局变量,并检查返回地址是否在边界内。

所有这些都是为了极小的开销。但是,我们的第二个问题是,检索基址在每个平台中都是不同的。经过一些研究,我发现事情比预期的要简单:

  • 在 Windows/Win32 中,我们可以简单地做到这一点:

    #include <windows.h>
    #include <psapi.h>
    inline void __initialize_base_address()
    {
    MODULEINFO minfo;
    GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &minfo, sizeof(minfo));
    base_addr = (uintptr_t) minfo.lpBaseOfDll;
    base_end  = (uintptr_t) minfo.lpBaseOfDll + minfo.SizeOfImage;
    }
    
  • 在 Linux 中,有一千种方法可以做到这一点,包括链接器全局变量和一些历进程模块表的调试(冗长和不可靠)方法。我正在查看链接器映射输出,并注意到_init_fini函数似乎总是包装其余的.text部分符号。有时很难找到在任何地方都有效的最简单的解决方案:

    #include <link.h>
    inline void __initialize_base_address()
    {
    void *handle = dlopen(0, RTLD_NOW);
    base_addr = (uintptr_t) dlsym(handle, "_init");
    base_end  = (uintptr_t) dlsym(handle, "_fini");
    dlclose(handle);
    }
    
  • 而在macOS中,事情的记录更少,我不得不使用达尔文内核开源代码拼凑我自己的东西,并追踪一些晦涩的低级工具作为参考。请记住,_NSGetMachExecuteHeader()只是内部链接器全局_mh_execute_header的包装器。如果您需要对解析Mach-O格式及其结构做任何事情,那么getsect.h就是要走的路:

    #include <mach-o/getsect.h>
    #include <mach-o/ldsyms.h>
    #include <crt_externs.h>
    inline void __initialize_base_address()
    {
    size_t size;
    void *ptr = getsectiondata(&_mh_execute_header, SEG_TEXT, SECT_TEXT, &size);
    base_addr = (uintptr_t) _NSGetMachExecuteHeader();
    base_end  = (uintptr_t) ptr + size;
    }
    

要记住的另一件事是,这个其他一些 cpp-module-正在使用我们的内部分配器-全局覆盖-新-导致-奇怪的错误问题似乎是 Linux 和 macOS 中的一个问题,我在 Windows 中没有这个问题,可能是因为在此过程中没有加载冲突的 DLL,主要是基于 C API。我认为,或者平台可能为每个模块使用不同的C++运行时。

我遇到的主要问题是由 Mesa3D 引起的,它使用LLVM(纯C++进出)进行许多 GLSL 着色器编译器,并且喜欢不请自来地吞噬我的小型定制内存领域的大块。

重写结构上依赖于这些分配器的遗留程序是不可能的,因为它的规模和复杂性,所以事实证明这是使事情按预期工作的最佳方式。

它只是几行可选的、偷偷摸摸的、额外的每个平台的代码。