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