c++ - 使用自定义分配器及其替代方案重载基本类型

标签 c++ templates operator-overloading global-variables dynamic-allocation

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

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

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


考虑到 C++ 不允许有用的东西,比如每个 namespace 有单独的分配器,或者限制每个可执行模块的 new 运算符,模拟这个的最好方法是什么在基本数据类型中,我不能直接对 int 进行子类化?

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

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

最佳答案

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


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

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

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

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

  • 在 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 中,文档记录更少,但我不得不使用 Darwin 内核开源代码拼凑自己的东西,并追踪一些晦涩的低级工具作为引用。请记住 _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;
    }
    

要记住的另一件事是,这个 some-other-cpp-module-is-using-our-internal-allocator-that-globally-overrides-new-causing-weird-bugs 问题似乎是Linux 和 macOS,我在 Windows 中没有这个问题,可能是因为在此过程中没有加载冲突的 DLL,主要是基于 C API。我认为,或者平台可能为每个模块使用不同的 C++ 运行时。

我遇到的主要问题是由 Mesa3D 引起的,它使用 LLVM(纯 C++ 输入和输出)用于他们的许多 GLSL 着色器编译器,并且喜欢不请自来地吞噬我的小定制内存空间的大块。

重写一个在结构上依赖于这些分配器的遗留程序由于其庞大的规模和复杂性是不可能的,因此事实证明这是使事情按预期工作的最佳方式。

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

关于c++ - 使用自定义分配器及其替代方案重载基本类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44510812/

相关文章:

c++ - 清除组合 C/C++ 文件中的错误

c++ - gdb中的nexti和stepi有什么区别?

c++ - 为每个函数和自由函数 boost mpl

c++ - 为模板类重载 const 版本的 [] 运算符

G++ 4.5 中 std::complex 的 C++11 复制赋值 - 与 'operator+' 不匹配

c++ - 较小的管道如何加速数据流?

C++追加到字符串并写入文件

c++ - C++中的转发参数

c++ - 模板别名如何影响模板参数推导?

c++ - 重载运算符>>,将一个参数传递给对象而不是三个