【问题标题】:Issues when memory allocation/deallocation when linking with static c runtime与静态 c 运行时链接时内存分配/释放时的问题
【发布时间】:2012-08-31 12:46:40
【问题描述】:

我在 Jeffrey Richter 和 Christophe Nasarre 所著的 Windows via C-C++ 一书中看到了以下注释。

检查以下代码: V

OID EXEFunc() {
PVOID pv = DLLFunc();
// Access the storage pointed to by pv...
// Assumes that pv is in EXE's C/C++ run-time heap
free(pv);
}
PVOID DLLFunc() {
// Allocate block from DLL's C/C++ run-time heap
return(malloc(100));
}

那么,你怎么看?前面的代码能正常工作吗?是由分配的块 DLL的函数被EXE的函数释放了?答案是:也许。显示的代码不 给你足够的信息。如果 EXE 和 DLL 都链接到 DLL C/C++ 运行时 库,代码工作得很好。但是,如果一个或两个模块链接到静态 C/C++ 运行时库,调用 free 失败。

我无法理解为什么在将模块与静态 C 运行时链接时调用 free 会失败。

有人可以解释为什么免费会失败吗? 在这里找到类似的问题: Memory Allocation in Static vs Dynamic Linking of C Runtime

但我和 MrPhilTx 有同样的疑问: 不是所有的堆都在同一个地址空间中吗?

谢谢!

【问题讨论】:

    标签: c++ c visual-c++


    【解决方案1】:

    当您的 DLL 和 EXE 都静态链接到 C 运行时,这两个运行时根本不知道彼此。因此,EXE 和 DLL 都有自己的运行时副本、自己的堆和堆元数据。双方都不知道其他元数据,并且在释放内存时没有安全的方法来更新数据。你最终会得到不稳定的元数据,事情最终会失败(如果你很幸运,它会立即失败)。

    这意味着您的进程中至少有两个堆,每个堆都有自己的规则和元数据。 EXE 无法知道 DLL 分配内存的确切方式,因此无法释放它。

    至于为什么当一切都是动态链接的时候你可以共享一个堆,这很简单,进程中只有一个 C 运行时 DLL 的副本,所以如果每个 DLL 链接到它,它们都会调用具有相同元数据的相同代码。

    【讨论】:

    • 即使链接到 EXE 和 DLL 的运行时代码是相同的,元数据存在单独副本的事实也会使一切变得混乱。在一个堆上分配并释放到另一个堆会导致一大堆混乱。
    【解决方案2】:

    您不能从一个分配器分配内存并用另一个分配器释放它。不同的分配器使用不同的内部实现,将内存块分配给没有分配它的分配器的结果是不可预测的。

    因此,除非您知道两段代码使用相同的分配器这一事实,否则您不能在一段代码中分配内存并在另一段中释放内存。通常的解决方案是确保同一个单元分配和释放内存。在您的示例中,DLL 可以提供主代码可以调用的“免费”函数,而不是调用自己的 free 函数,该函数释放到自己的分配器。

    所以改为这样做:

    OID EXEFunc() {
        PVOID pv = DLLFunc();
        // Access the storage pointed to by pv...
        // Assumes that pv is in EXE's C/C++ run-time heap
        DLLFreeFunc(pv);
    }
    
    ...
    
    PVOID DLLFunc() {
        // Allocate block from DLL's C/C++ run-time heap
        return(malloc(100));
    }
    
    DLLFreeFunc(PVOID x) {
        free(x);
    }
    

    【讨论】:

      【解决方案3】:

      在 Linux 上,程序使用 brk 和 sbrk 系统调用从内核请求额外的数据页。 sbrk 返回一个地址,指向您的程序可以使用的数据段。

      malloc 和 free 通过将 brk 和 sbrk 返回的数据段变成一个堆来使用它。堆是当前进程空间中的一大块内存,可以根据需要请求和返回小块内存。请务必注意,对 malloc 和 free 的许多调用都不会进行系统调用。

      现在,当 malloc 和 free 想要使用堆时,他们需要获取指向堆的指针。此指针存储在称为静态数据的单独数据段中,并在应用程序加载时分配。为了确保不同的 DLL(或 linux 上的共享库)不会相互冲突,每个 DLL 都有自己的静态数据段。

      现在让我们假设 dll 和可执行文件都静态链接到它们自己的库。在这种情况下,dll 和可执行文件将具有指向不同堆的指针,并且在这种情况下,dll 和可执行文件都必须释放它们自己的内存。

      但是在 linux 上,dll 和可执行文件都将通过一个通用 DLL(linux 上的 libc.so)访问 malloc 和 free。在这种情况下,由于 dll 和可执行文件都在有效地访问 libc 的堆,因此可执行文件可以安全地释放 dll 分配的内存。

      无论如何,最好让 dll 提供自己的免费功能。如果没有其他文件表明需要释放 DLLFunc 返回的指针,则此内容。

      我想这在 Windows 上也是如此。

      【讨论】:

      • 由于 CRT 到共享库和 EXE 的静态链接,您能否详细说明或共享进程中不同堆上的一些链接。我找不到太多信息。每个地方都说每个进程只有一个堆。
      【解决方案4】:

      代码严重依赖于mallocfree 的实现。一个好的实施没有问题,一个糟糕的实施确实会失败。创建mallocfree 的工作DLL 实现肯定更容易,但在静态库中这样做远非不可能。

      一个简单的例子是一个静态库,它将调用直接转发到GlobalAllocGlobalFree

      【讨论】:

      • 您的意思是这里出现问题是因为 exe 和 dll 可能链接到了 malloc/free 的两个不同实现吗?
      • @Suhas:不是这样。拥有两个不同的实现本身就很麻烦,无论是在 2 个 DLL 还是 2 个静态库中。
      猜你喜欢
      • 2011-12-30
      • 2017-06-21
      • 1970-01-01
      • 2013-08-29
      • 1970-01-01
      • 2013-08-31
      • 1970-01-01
      • 1970-01-01
      • 2012-07-27
      相关资源
      最近更新 更多