我尝试在一些具有不同处理器和主内存大小的不同机器上运行此代码。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// your code goes here
int *a, i;
a = (int *)malloc(10*(sizeof(int)));
for(i = 0; i < 10; i++)
{
a[i] = i*i;
}
free(a);
a = (int *)malloc(10*(sizeof(int)));
for(i = 0; i < 10; i++)
{
printf("%dn",a[i]);
}
free(a);
return 0;
}
但是,在所有机器中,它都会生成相同的输出:
0
1
4
9
16
25
36
49
64
81
我的问题是:变量"a"是否应该始终分配使用相同的内存位置执行?
从理论上讲,这可能不是强制性的。如果我的理解是正确的:每个 malloc 的 'a' 都可以分配不同的基址。
然而,现代计算机在实践中实际发生了什么?
现代内存分配器具有多种性能优化。它们具有多个可用内存桶,每次您请求一定数量的字节时,它们都会查看并确定可用的最佳内存区域。一些内存分配器会保留指向您上次分配的指针,因此如果您需要另一个相同大小的块(经常发生),它们会返回相同的部分,这样他们就不必寻找新的,这需要时间。
您可以尝试两次调用malloc()
,然后调用free()
,然后再次调用malloc()
以查看会发生什么情况。
它在实践中发生,因为重用刚刚释放的内存块通常是最佳选择。 它可能在缓存中仍然很热,这意味着 malloc 的自由列表可以缩短(而不是将该块留在免费列表中并从操作系统获取一个新块)。
这也意味着malloc
可能不需要任何系统调用来满足此请求。 小free()
调用通常不会立即返回到操作系统(因为通常它们不能,因为它们只是页面的一部分)。 因此,典型的malloc实现将它们放在免费列表中。
所以这个小实验告诉你一些关于malloc在你尝试过的特定C实现上的内部实现。 正如我认为您从问题措辞中意识到的那样,这是未定义的行为,永远不应该依赖。
它并不是真正特定于 CPU 架构的。 AFAIK,GNU libc的malloc
在每个架构上使用相同的算法。
即使在以这种方式工作的 C 实现上,也有很多方法可以打破这种情况:信号处理程序可以在自由和第二个 malloc 之间运行,并获取块。 在多线程代码中,另一个线程可以在自由和malloc之间获取该块。 (尽管这不太可能:每个线程通常使用自己的小池)。
一个大的缓冲区通常会在free()
调用中返回给操作系统,因此下一个malloc()
必须再次从操作系统获取新内存。 请参阅mallopt(3)
中的M_MMAP_THRESHOLD
,了解glibc的malloc中可调参数的解释。 M_PERTURB
提供的功能与 Adrian对 MSVC++ 调试版本描述的功能类似:破坏具有释放后使用错误的代码,以及破坏依赖于malloc
ed 内存归零或其他内容的代码(使用 calloc
有效地获得清零内存)。
不能保证这会起作用。 (事实上,它不适用于 Visual Studio 中的调试版本,因为调试内存分配器会在您释放数据以检测此类错误时破坏数据。
您永远不应该依赖这种行为。
某些内存管理器使用释放内存块的 LIFO(后进先出)列表。这意味着相同大小的下一个分配可能会返回相同的块。 其他内存管理器明确不使用 LIFO 免费列表来使恶意软件更难利用堆管理错误。