【问题标题】:How does free know how much to free?免费如何知道要免费多少?
【发布时间】:2017-07-25 21:29:57
【问题描述】:

在 C 编程中,您可以将任何类型的指针作为参数传递给 free,它如何知道要释放的已分配内存的大小?每当我传递一个指向某个函数的指针时,我还必须传递大小(即一个包含 10 个元素的数组需要接收 10 作为参数才能知道数组的大小),但我不必将大小传递给免费功能。为什么不呢,我可以在我自己的函数中使用相同的技术来避免我需要围绕数组长度的额外变量进行购物车吗?

【问题讨论】:

标签: c size pointers free


【解决方案1】:

当您调用malloc 时,堆管理器将属于已分配块的内存量存储在某处。

我自己从未实现过,但我猜分配块前面的内存可能包含元信息。

【讨论】:

  • 这是一种可能的实现方式,但可以设计一个系统,在一个完全不同的页面中的单个表中跟踪所有内存,而不必靠近分配的内存池。
【解决方案2】:

当您调用malloc() 时,您指定要分配的内存量。实际使用的内存量略多于此,并且包括记录(至少)块有多大的额外信息。你不能(可靠地)访问其他信息——你也不应该:-)。

当您调用free() 时,它只会查看额外信息以找出块有多大。

【讨论】:

  • 仅供参考,例如 BSD 有 malloc_size() 可以从 malloc()ed 指针可靠地访问块大小。但是没有可靠、便携的方式。
  • 我认为重要的是要说这个额外的信息块位于返回的指针之前。
  • @gs 这取决于实现。但是,是的,通常就是这样。
  • 如果free() 要求程序员准确地报告malloc() 块有多大,你能想象这有多可怕吗?内存泄漏已经够糟糕了。
  • 为什么malloc()free() 可以使用这些信息,但您必须存储数组的大小?如果他们仍然要存储信息,为什么他们不能做类似blockSize(ptr) 之类的事情?
【解决方案3】:

malloc()free() 取决于系统/编译器,因此很难给出具体答案。

更多信息on this other question

【讨论】:

  • 它们确实依赖于库(通常是 C 库,通常与操作系统密切相关)。对于编译器来说,它们只是函数。
【解决方案4】:

来自comp.lang.c 常见问题列表:How does free know how many bytes to free?

malloc/free 实现在分配时会记住每个块的大小,因此在释放时无需提醒它大小。 (通常,大小存储在分配的块附近,这就是为什么如果分配块的边界稍微超出一点,事情通常会很糟糕)

【讨论】:

  • 这是一个非答案。问题正是这样:为什么 free 可以可靠地查找块的大小,但程序员却没有可用的函数来做到这一点?
  • 这确实是 malloc api 的实现细节,并且没有 api 以标准方式获取此信息(据我所知)。 “系统”记录它并在free 上使用它。也许答案不让你满意,但我认为你不会得到一个更通用的信息:-)
【解决方案5】:

回答你问题的后半部分:是的,你可以,C 中相当常见的模式如下:

typedef struct {
    size_t numElements
    int elements[1]; /* but enough space malloced for numElements at runtime */
} IntArray_t;

#define SIZE 10
IntArray_t* myArray = malloc(sizeof(intArray_t) + SIZE * sizeof(int));
myArray->numElements = SIZE;

【讨论】:

  • 这与 BSD malloc 用于小对象的技术完全不同(尽管它是创建 Pascal 样式数组的完美技术)
【解决方案6】:

在相关注释GLib 库具有不保存隐式大小的内存分配函数 - 然后您只需将大小参数传递给 free。这可以消除部分开销。

【讨论】:

    【解决方案7】:

    这个答案是从How does free() know how much memory to deallocate? 重新定位的,在那里我被一个明显重复的问题突然阻止回答。那么这个答案应该与这个副本相关:


    对于malloc 的情况,堆分配器存储原始返回指针到稍后free 内存所需的相关细节的映射。这通常涉及以与正在使用的分配器相关的任何形式存储内存区域的大小,例如原始大小,或用于跟踪分配的二叉树中的节点,或正在使用的内存“单元”的计数。

    free 不会失败,如果你“重命名”指针,或者以任何方式复制它。然而,它没有被引用计数,只有第一个 free 是正确的。其他 frees 是“双重释放”错误。

    尝试free 任何指针的值与之前的mallocs 返回的值不同,并且尚未释放是一个错误。无法部分释放从malloc 返回的内存区域。

    【讨论】:

    【解决方案8】:

    C 内存分配函数的大多数实现都将存储每个块的记帐信息,无论是内联还是单独存储。

    一种典型的方式(内联)是实际分配标头和您要求的内存,并填充到某个最小大小。所以举个例子,如果你要求 20 字节,系统可能会分配一个 48 字节的块:

    • 16 字节标头包含大小、特殊标记、校验和、指向下一个/上一个块的指针等。
    • 32 字节数据区域(您的 20 字节填充为 16 的倍数)。

    然后给你的地址就是数据区的地址。然后,当您释放该块时,free 将简单地获取您提供的地址,并且假设您没有填充该地址或它周围的内存,请检查紧接在它之前的会计信息。从图形上看,这将是:

     ____ The allocated block ____
    /                             \
    +--------+--------------------+
    | Header | Your data area ... |
    +--------+--------------------+
              ^
              |
              +-- The address you are given
    

    请记住,标题的大小和填充完全是实现定义的(实际上,整个事情都是实现定义的 (a) 但内联记帐选项是一个常见的选项) .

    如果您将它们覆盖或释放两次,则会计信息中存在的校验和和特殊标记通常会导致错误,例如“内存领域已损坏”或“双重释放”。

    填充(使分配更有效)是为什么您有时可以在请求空间的末尾写一点而不引起问题(不过,不要这样做,这是未定义的行为,只是因为它有时有效, 不代表可以这样做)。


    (a) 我已经在嵌入式系统中编写了 malloc 的实现,无论您要求什么,您都可以获得 128 个字节(这是系统中最大结构的大小),假设您要求 128 字节或更少(更多的请求将得到 NULL 返回值)。一个非常简单的位掩码(即非内联)用于决定是否分配了 128 字节的块。

    我开发的其他程序有 16 字节块、64 字节块、256 字节块和 1K 块的不同池,同样使用位掩码来决定哪些块已使用或可用。

    这两个选项都设法减少了会计信息的开销并提高了mallocfree 的速度(释放时无需合并相邻块),这在我们工作的环境中尤其重要。

    【讨论】:

    • @paxdiablo 这是否意味着 malloc 不分配连续的内存块?
    • @user10678,malloc 唯一真正的要求是,对于成功的案例,它为您提供至少与您要求的一样大的内存块。就您访问其中元素的方式而言,各个块是连续的,但不要求块来自的竞技场是连续的。
    • 相关问题:为什么没有malloc/free的变体,释放的时候在哪里指定大小就不用存储大小了?
    • @user253751,因为除了指针本身之外,您还需要跟踪更多件事。这是不必要的危险:void *x = malloc(200); free(x, 500); 不会结束好:-) 无论如何,为了提高效率,实际大小缓冲区可能更大(你不能依赖这个)。
    • @paxdiablo 它还避免浪费内存来保存大小。
    【解决方案9】:

    最初的技术是分配一个稍大的块并在开始时存储大小,然后将博客的其余部分交给应用程序。额外的空间包含一个大小,并可能链接到将空闲块连接在一起以供重用。

    但是,这些技巧存在某些问题,例如糟糕的缓存和内存管理行为。在块中直接使用内存往往会不必要地对内容进行分页,并且还会创建脏页,从而使共享和写时复制变得复杂。

    所以更高级的技术是保留一个单独的目录。还开发了异乎寻常的方法,其中内存区域使用相同的 2 次方大小。

    一般来说,答案是:分配一个单独的数据结构来保持状态。

    【讨论】:

      【解决方案10】:

      当我们调用 malloc 时,它只是从它的要求中消耗更多的字节。这种更多的字节消耗包含校验和、大小和其他附加信息等信息。 当我们当时调用 free 时,它​​会直接转到找到地址的附加信息,并找出有多少块是空闲的。

      【讨论】:

        【解决方案11】:

        回答第二个问题,是的,您可以(有点)使用与malloc() 相同的技术 通过简单地将每个数组中的第一个单元格分配给数组的大小。 这使您无需发送额外的大小参数即可发送数组。

        【讨论】:

          猜你喜欢
          • 2011-12-08
          • 1970-01-01
          • 2015-02-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多