gpt4 book ai didi

c++ - 跨共享/静态库集成C++自定义内存分配器

转载 作者:行者123 更新时间:2023-12-01 14:20:34 32 4
gpt4 key购买 nike

我开始在项目中使用一些自定义内存分配器,例如rpmallocltmalloc,但我对集成有所担忧,我的项目具有构建为共享库或静态库的各种内部模块(取决于我在构建系统中的配置方式),并且应该为Windows / Linux / FreeBSD / Mac OS X和诸如x86和ARM之类的体系结构构建/运行,而且我不知道是否应该在头文件中调用内存分配器集成,还是应该保留在其中cpp文件。

如果内存分配器调用保留在头文件中,则每个模块都应链接该内存分配器的静态库,如果它保存在.cpp文件中,则调用将包含在包含它们的库中,并且只有该模块才应链接该内存分配器。自定义内存分配器,但是该模块应该包含一个接口(interface),每个模块都可以分配它们(避免内存分配不一致)

如果已正常分配内存(如malloc / free / syscalls一样),则我已阅读here,每个共享库都有自己的堆,但是如果使用mmap,则分配的内存不属于程序堆。

我的问题是,如果将共享/静态库保存在一个库中,是否会对我的共享/静态库造成任何危害(但每个其他库都应将其链接以便访问其内存分配接口(interface))?还是所有内容都应内联在头文件中,并且每个库都应链接内存分配器库?

最佳答案

内存分配的完成方式在很大程度上取决于操作系统。您需要了解共享库在这些操作系统中的工作方式,C语言如何与那些操作系统以及共享库的概念相关。
C,C++和模块化编程
首先,我想提一下C语言不是模块化语言,例如它不支持模块或模块化编程。对于诸如C和C++之类的语言,模块化编程的实现由底层操作系统决定。共享库是用于用C和C++实现模块化编程的机制的示例,因此我将它们称为模块。
模块=共享库和可执行文件
Linux和类Unix系统
最初,Unix系统上的所有内容都是静态链接的。共享库稍后发布。而且,由于Unix是C语言的起点,因此这些系统试图提供与C语言编程相近的共享库编程接口(interface)。
这个想法是,应该在不考虑共享库的情况下构建最初编写的C代码,并且应该在不更改源代码的情况下工作。结果,所提供的环境通常具有由所有加载的模块共享的单个进程范围的符号 namespace ,例如在整个过程中,只有一个名称为foo的函数,除了static函数(以及某些使用OS特定机制的模块中的hidden)除外。基本上与静态链接相同,在静态链接中,不允许使用重复的符号。
这对于您的情况意味着在整个过程中始终使用一个名为malloc的函数,并且每个模块都在使用它,例如所有模块共享相同的内存分配器
现在,如果进程碰巧具有多个malloc函数,则仅选择一个函数并将其用于所有模块。这里的机制非常简单-共享库不知道每个引用函数的位置,因此它们通常会通过一些表(GOTPLT)来调用它们,该表在第一次调用或加载时会懒洋洋地填充所需的地址。提供原始功能的模块采用了相同的规则-即使在同一表中也将在内部调用此功能,即使在提供该功能的原始模块中也可以覆盖该功能(这是许多与效率低下有关的原因在Linux上使用共享库,请搜索-fno-semantic-interposition-fno-plt来克服这一问题)。
这里的一般规则是,引入符号的第一个模块将是提供符号的模块。因此,原始流程可执行文件在此处具有最高优先级,如果它定义了malloc函数,则该malloc函数将在流程中的任何地方使用。这同样适用于callocreallocfree和其他函数。使用此类技巧和类似LD_PRELOAD的技巧可让您覆盖应用程序的“默认内存分配器”。由于存在一些极端情况,因此不能保证这能正常工作。在执行此操作之前,您应该查阅图书馆的文档。
我要特别指出,这意味着在所有模块共享的进程中只有一个堆,这是有充分理由的。类似Unix的系统通常提供两种在进程中分配内存的方式:

  • brksbrk系统调用
  • mmap系统调用

  • 第一个可让您访问通常在可执行镜像之后直接分配的每个进程内存区域。由于只有一个这样的区域,因此这种内存分配方式只能由进程中的单个分配器使用(并且您的C库通常已经使用了它)。
    在将任何自定义内存分配器放入进程之前,必须了解这一点很重要-它要么不应该使用 brksbrk,要么应该覆盖C库的现有分配器。
    第二个可用于直接从底层内核请求内存块。内核知道进程虚拟内存的结构后,便能够分配内存页面,而不会干扰任何用户空间分配器。这也是在进程中具有多个完全独立的内存分配器(堆)的唯一方法。
    视窗
    Windows不像类似Unix的系统那样依赖C运行时。相反,它提供了自己的运行时-Windows API。
    Windows API有两种分配内存的方法:
  • 使用类似于VirtualAllocMapViewOfFile的功能。
  • 和堆分配功能-HeapCreateHeapAlloc

  • 第一个等效于 mmap,而第二个等效于 malloc的更高级版本,该版本基于 VirtualAlloc内部(如我所相信)。
    现在,由于Windows与C语言的关系不像Unix一样,因此它无法为您提供 mallocfree函数。而是由C运行时库提供的,该库在Windows API之上实现。
    关于Windows的另一件事-它没有每个进程符号 namespace 单一的概念,例如您不能像在类似Unix的系统上一样重写函数。这使您可以在同一进程中同时存在多个C运行时,并且每个运行时都可以提供 mallocfree等的独立实现,每个运行在单独的堆上。
    因此,在Windows上,所有库都将共享一个特定于Windows API进程的进程(可以通过 GetProcessHeap获得),同时它们将共享该进程中C个运行时之一的堆。
    那么如何将内存分配器集成到程序中呢?
    这取决于。您需要了解您要实现的目标。
    您是否需要替换过程中每个人使用的内存分配器,例如默认分配器?这仅在类Unix系统上可行。
    这里唯一可移植的解决方法是显式使用您的特定分配器接口(interface)。这样做并不重要,只需要确保Windows上的所有库共享同一堆即可。
    这里的一般规则是,要么应将所有内容静态链接,要么应将所有内容动态链接。两者之间的某种混合可能真的很复杂,并且需要您将整个体系结构保持在头脑中,以避免在程序中混合堆或其他数据结构(如果您没有很多模块,这不是一个大问题) 。如果需要混合使用静态链接和动态链接,则应将分配器库构建为共享库,以使其在流程中具有单个实现更加容易。

    Unix-like和Windows之间的另一个区别是Windows没有“静态链接的可执行文件”的概念。在Windows上,每个可执行文件都依赖于Windows特定的动态库,例如 ntdll.dll。对于ELF,可执行文件具有“静态链接”和“动态链接”可执行文件不同的类型。
    这主要是由于每个进程的单个符号 namespace 造成的,这使得在类似Unix的环境中混合共享链接和静态链接变得很危险,但允许Windows很好地混合静态链接和动态链接(几乎,不是真的)。
    如果您使用一个库,则应确保将其与动态链接的可执行文件动态链接。想象一下,如果您将分配器静态链接到共享库,但是进程中的另一个库也使用相同的库-您可能是偶然使用了另一个分配器,而不是您所期望的。

    关于c++ - 跨共享/静态库集成C++自定义内存分配器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47372194/

    32 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com