如何在C中释放特定大小的内存分配

3
我使用malloc分配了100字节的内存。然而最终只用了其中的40字节。有两种情况:
  1. 使用的40字节在前面,剩下的60字节没有使用。
  2. 使用的40字节在后面,剩下的60字节没有使用。
我始终拥有一个指向未使用内存的指针和未使用内存的大小信息。由于free函数会认为要释放全部100字节的内存,因此不能使用它来释放60字节的内存。问题是,如何在这两种情况下释放60字节的内存?

3
使用realloc函数来处理场景1。 - Christopher Moore
在第二种情况(“60/40”)中,更容易的方法是简单地使用malloc()分配一个新块,将40个字节复制过去,然后释放原始的100个字节块... “幕后”(正如内存管理器所说),这甚至可能是realloc()在情况#1中选择执行的操作。 - Fe2O3
1
你只能在 malloc() 返回的原始地址上调用 free()。你不能调用 free() 传递原始块中第一个之后的某个地址。 - David C. Rankin
4个回答

4

函数realloc允许您缩小已分配内存的大小。

然而,在您的问题示例中,如果您使用realloc将数组从100字节缩小到40字节,则会保留前40字节的数据并丢失后60字节的数据。因此,这只适用于您问题的情况#1。

如果您还希望它在情况#2中起作用,则必须首先将内存缓冲区后面的40个字节复制到内存缓冲区的前面,例如使用memcpy

然而,一些操作系统确实提供了一种释放连续内存区域后面部分的方法,而不是前面部分。例如,在Linux上是mmap/mremap,在Microsoft Windows上是VirtualFree。但这样的函数通常只能处理单个内存页面,它们通常至少有4096字节大小,因此这些功能只在处理更大量的内存时才有用。它们在你问题的示例中并没有用处,因为该示例仅涉及100字节。

ISO C标准库本身不提供上述段落中提到的功能。因此,如果您希望代码保持可移植性,则只能使用realloc


2
realloc 并不保证能够重用块中多余的内存。许多实现在将 100 字节的块重新分配为 40 字节时可能什么也不做(尽管如果您将 100 兆字节的块重新分配为 40 兆字节,则可能会执行某些操作)。 - rici
通过使用gcc x86_64进行检查,循环100次分配1-100字节并检查ptrdiff_t与先前的分配情况,似乎gcc的最小分配为32字节,并且以16字节增量递增,可能在1K左右的mmap块内。您可以进一步调查stracing或转储到汇编。 - David C. Rankin
@kuljot 请检查你的编译器,在x86_64架构上内存会对齐到16个字节,因此选择16的倍数可能会更好地利用尽可能多的内存空间。 - David C. Rankin
2
@picchiolu:一些内存管理器实施措施以减少堆碎片问题,例如通过从与小内存分配不同的堆中提供大内存分配。因此,堆碎片可能是一个问题,也可能不是一个问题,这取决于具体情况。 - Andreas Wenzel
2
@picchiolu - 当然 gcc x86_64 最小分配大小 过期时间:2023年1月20日,中国标准时间上午12:29:25。 - David C. Rankin
显示剩余5条评论

2

realloc 可以用来减少(或增加)之前由 malloccallocrealloc 返回的内存分配的大小。

但是,在这样做时,该内存的先前指针值将无效。

realloc 相对于其开头调整分配的大小,因此,如果数据位于分配的末尾,则必须将该数据向前移动。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TOTAL 100
#define USED 40
#define UNUSED 60

int main(void)
{
    int *front_used = calloc(TOTAL, sizeof *front_used);
    memset(front_used, 0, USED * sizeof *front_used);

    int *ftemp = realloc(front_used, USED * sizeof *front_used);
    /* ftemp should be checked for NULL */
    front_used = ftemp;

    int *back_used = calloc(TOTAL, sizeof *back_used);
    memset(back_used + UNUSED, 0, USED * sizeof *back_used);
    /* must move data to start of buffer */
    memmove(back_used, back_used + UNUSED, USED * sizeof *back_used);

    int *btemp = realloc(back_used, USED * sizeof *back_used);
    /* btemp should be checked for NULL */
    back_used = btemp;
}

1
这似乎是一种内存碎片化的解决方案,我宁愿不去移动小块内存。 - picchiolu

1
TLDR:您不应该释放部分内存块。而且,这些小的优化可能会对程序产生负面影响,因此您不必担心。
虽然这种想法在初学者中很常见,他们试图尽可能精确地使用内存,但实际上这样做并不是一个好主意。这是因为内存管理器在内部工作时的方式。
为了让您有一些想法,让我试着用一个相对较小的上下文来解释一下。那么,您认为内存管理器如何知道从给定指针开始需要释放多少块内存?
最常见的实现方式,也是我所知道的实现方式,是内存管理器在指针指向的内存之前的字节中存储一块元数据块。元数据包括帮助内存管理器正确执行其工作的信息——存储与其关联的下一个内存块的大小,存储下一个可用的空闲内存块的位置等。
假设您以以下方式分配了100个字节的内存:
char* characterPointer = malloc(sizeof(char)*100);

“而且正如您在问题中提到的那样,您使用了一些从这些100个分配的插槽中的内存(比如40)。
现在,如果您尝试释放从第41个索引开始的内存,就像以下的示例:”
free(characterPointer+40);

内存管理器将向左移动一个字节,并尝试将存在的位模式解析为给定指针的元数据。现在你觉得会发生什么?嗯,答案是我自己也不知道,可能取决于情况。但可以肯定的一件事是它会导致一些不希望发生的行为。
它可能释放了比你想要的更多或更少的内存,无论如何,都会破坏内存。通常最好不要关注这样小的内存块。
内存管理器是用来做类似的事情的。例如,有时候当你请求100个字节的内存时,内存管理器可能会返回一个大于100个字节的内存块,这完全没问题,因为它满足了你的内存需求。这些决策是在后台进行的,以提高管理器的速度,每个内存管理器实现都有所不同。

0

你不能部分释放之前由malloc分配的内存块。最接近的选项是使用realloc来调整之前由malloc分配的内存块的大小。

此外,请注意,释放的内存块不会立即变为操作系统可用。它们被列为空闲块,并且首先可以通过malloc访问以供将来分配。

最后,分配和释放小块内存可能会导致内存碎片化,这可能比您试图解决的问题更糟糕。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接