在 C 编程中,您可以将任何类型的指针作为参数传递给 free,它如何知道要释放的已分配内存的大小?每当我传递一个指向某个函数的指针时,我还必须传递大小(即一个包含 10 个元素的数组需要接收 10 作为参数才能知道数组的大小),但我不必将大小传递给免费功能。为什么不呢,我可以在我自己的函数中使用相同的技术来避免我需要围绕数组长度的额外变量进行购物车吗?
11 回答
当您调用 时malloc()
,您指定要分配的内存量。实际使用的内存量略多于此,并且包括记录(至少)块有多大的额外信息。您不能(可靠地)访问其他信息-也不应该:-)。
当您调用 时free()
,它只是查看额外信息以找出块有多大。
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 块的不同池,再次使用位掩码来决定哪些块被使用或可用。
这两个选项都设法减少了会计信息的开销并提高了malloc
和free
(释放时无需合并相邻块)的速度,这在我们工作的环境中尤其重要。
来自comp.lang.c
FAQ 列表:free 如何知道要释放多少字节?
malloc/free 实现在分配时会记住每个块的大小,因此在释放时无需提醒它大小。(通常,大小存储在分配的块附近,这就是为什么如果分配块的边界稍微超出范围,事情通常会很糟糕)
这个答案重新定位自Free() 如何知道要释放多少内存?我被一个明显重复的问题突然阻止回答。那么这个答案应该与这个副本相关:
对于 的情况,堆分配器存储原始返回指针的映射,到稍后获取内存malloc
所需的相关细节。free
这通常涉及以与使用中的分配器相关的任何形式存储内存区域的大小,例如原始大小,或用于跟踪分配的二叉树中的节点,或正在使用的内存“单元”的计数。
free
如果您“重命名”指针或以任何方式复制它,则不会失败。然而,它没有被引用计数,只有第一个free
是正确的。附加free
s 是“双重免费”错误。
尝试free
使用与先前 s 返回的值不同malloc
且尚未释放的任何指针是错误的。无法部分释放从malloc
.
在相关说明中, GLib库具有不保存隐式大小的内存分配函数 - 然后您只需将大小参数传递给 free。这可以消除部分开销。
当您调用时,堆管理器将属于已分配块的内存量存储在某处malloc
。
我自己从未实现过,但我猜分配块前面的内存可能包含元信息。
malloc()
并且free()
依赖于系统/编译器,因此很难给出具体的答案。
关于这个其他问题的更多信息。
最初的技术是分配一个稍大的块并在开始时存储大小,然后将博客的其余部分交给应用程序。额外的空间包含一个大小,并可能链接到将空闲块连接在一起以供重用。
然而,这些技巧存在某些问题,例如糟糕的缓存和内存管理行为。在块中使用内存往往会不必要地对内容进行分页,并且还会创建脏页,从而使共享和写时复制变得复杂。
所以更高级的技术是保留一个单独的目录。还开发了异乎寻常的方法,其中内存区域使用相同的二次幂大小。
一般来说,答案是:分配一个单独的数据结构来保持状态。
回答你问题的后半部分:是的,你可以,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;
当我们调用 malloc 时,它只是从它的要求中消耗更多的字节。这种更多的字节消耗包含校验和、大小和其他附加信息等信息。当我们当时调用 free 时,它会直接转到找到地址的附加信息,并找到有多少块是空闲的。
要回答第二个问题,是的,您可以(有点)使用与malloc()
简单地将每个数组中的第一个单元格分配给数组的大小相同的技术。这使您可以在不发送额外大小参数的情况下发送数组。