提问人:SHH 提问时间:11/7/2011 最后编辑:sigjuiceSHH 更新时间:12/12/2021 访问量:51247
为什么 malloc 在 gcc 中将值初始化为 0?
Why does malloc initialize the values to 0 in gcc?
问:
也许它因平台而异,但是
当我使用 gcc 编译并运行下面的代码时,我每次在 ubuntu 11.10 中都得到 0。
#include <stdio.h>
#include <stdlib.h>
int main()
{
double *a = malloc(sizeof(double)*100)
printf("%f", *a);
}
为什么即使有 calloc,malloc 也会表现得像这样?
这是否意味着即使有时不希望将值初始化为 0,也会产生不必要的性能开销?
编辑:哦,我之前的例子不是启动,而是碰巧使用了“新鲜”块。
我正在寻找的是为什么它在分配一个大块时会初始化它:
int main()
{
int *a = malloc(sizeof(int)*200000);
a[10] = 3;
printf("%d", *(a+10));
free(a);
a = malloc(sizeof(double)*200000);
printf("%d", *(a+10));
}
OUTPUT: 3
0 (initialized)
但感谢您指出,定位错误时存在安全原因!(从来没想过)。当然,在分配新块或大块时,它必须初始化为零。
答:
你知道它肯定正在初始化吗?malloc() 返回的区域有没有可能一开始就经常有 0?
该标准没有规定应将值初始化为零。它只是在您的平台上碰巧它可能设置为零,或者在您读取该值的特定时刻可能已为零。malloc()
简短的回答:
它没有,它只是在你的情况下恰好是零。
(此外,您的测试用例未显示数据为零。它仅显示一个元素为零时。
长答案:
当您调用 时,将发生以下两种情况之一:malloc()
- 它回收以前分配的内存,并从同一进程中释放。
- 它从操作系统请求新页面。
在第一种情况下,内存将包含先前分配的剩余数据。所以它不会是零。这是执行小额分配时的常见情况。
在第二种情况下,内存将来自操作系统。当程序内存不足时,或者当您请求非常大的分配时,会发生这种情况。(就像你的例子一样)
这里有一个问题:出于安全原因,来自操作系统的内存将被清零。
当操作系统为您提供内存时,它本可以从不同的进程中释放出来。因此,内存可能包含敏感信息,例如密码。因此,为了防止您读取此类数据,操作系统会在将其提供给您之前将其归零。
*我注意到 C 标准对此没有说明。严格来说,这是一种操作系统行为。因此,这种归零可能存在于不考虑安全性的系统上,也可能不存在。
为了给大家更多的性能背景:
正如 @R. 在评论中提到的,这种归零就是为什么你应该始终使用 calloc
() 而不是 malloc()
+ memset()。
可以利用这一事实来避免单独的.calloc()
memset()
另一方面,这种归零有时是性能瓶颈。在某些数值应用中(例如不合时宜的FFT),您需要分配大量的暂存内存。用它来执行任何算法,然后释放它。
在这些情况下,归零是不必要的,相当于纯粹的开销。
我见过的最极端的例子是使用 48 GB 暂存缓冲区进行 70 秒操作的 20 秒清零开销。(大约 30% 的开销。(当然:机器确实缺少内存带宽。
显而易见的解决方案是简单地手动重用内存。但这通常需要突破已建立的接口。(特别是如果它是库例程的一部分)
评论
calloc
calloc
malloc
memset
malloc
memset
calloc
您的代码未演示将其内存初始化为 0。这可以由操作系统在程序启动之前完成。要查看 shich 是这种情况,请在内存中写入一个不同的值,释放它,然后再次调用 malloc。您可能会得到相同的地址,但您必须检查这一点。如果是这样,您可以查看它包含的内容。让我们知道!malloc
操作系统通常会清除发送到进程的新内存页,因此它无法查看旧进程的数据。这意味着当你第一次初始化一个变量(或malloc的东西)时,它通常为零,但如果你曾经重用过该内存(例如,通过释放它并再次malloc-ing),那么所有的赌注都是关闭的。
这种不一致正是为什么未初始化的变量如此难以找到的原因。
至于不必要的性能开销,避免未指定的行为可能更重要。在这种情况下,无论你能获得什么小的性能提升,都无法弥补你将不得不处理的难以发现的错误,如果有人稍微修改了代码(打破了以前的假设)或将其移植到另一个系统(假设可能一开始就无效)。
评论
为什么假设初始化为零?碰巧的是,第一次调用会导致对 或 系统调用,从而从操作系统分配一页内存。出于安全原因,操作系统有义务提供零初始化内存(否则,来自其他进程的数据将可见!所以你可能会认为 - 操作系统浪费时间将页面归零。但不是!在 Linux 中,有一个特殊的系统范围的单例页面,称为“零页面”,该页面将被映射为 Copy-On-Write,这意味着只有当您实际在该页面上写入时,操作系统才会分配另一个页面并初始化它。所以我希望这能回答你关于性能的问题。内存分页模型通过支持同一页面的多个映射功能以及在第一次写入时处理情况的能力,允许延迟使用内存。malloc()
malloc()
sbrk
mmap
如果调用 ,分配器会将该区域返回到其可用列表,当再次调用时,您可能会获得相同的区域,但与以前的数据相差无几。最终,可能会通过再次调用系统调用将内存返回到操作系统。free()
glibc
malloc()
free()
请注意,手册页严格说内存没有被清除,所以通过API上的“契约”,你不能假设它确实被清除了。以下是原始摘录:glibc
malloc()
malloc() 分配大小字节并返回指向已分配内存的指针。
内存未清除。如果 size 为 0,则 malloc() 返回 NULL, 或者以后可以成功传递给 free() 的唯一指针值。
如果您愿意,如果您担心性能或其他副作用,可以阅读有关该文档的更多信息。
永远不要指望任何编译器生成将内存初始化为任何内容的代码。malloc 只是返回一个指向 n 字节内存的指针,它甚至可能处于交换状态。
如果内存的内容至关重要,请自行初始化。
评论
从 gnu.org:
通过此实现,使用 mmap(匿名或通过 /dev/zero)分配非常大的块(比页面大得多)。
评论
我修改了您的示例以包含 2 个相同的分配。现在很容易看出不会将内存归零。malloc
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
{
double *a = malloc(sizeof(double)*100);
*a = 100;
printf("%f\n", *a);
free(a);
}
{
double *a = malloc(sizeof(double)*100);
printf("%f\n", *a);
free(a);
}
return 0;
}
使用 gcc 4.3.4 输出
100.000000
100.000000
评论
malloc
不会将内存初始化为零。它会按原样将其返回给您,而不会触及内存或更改其值。
那么,为什么我们会得到这些零呢?
在回答这个问题之前,我们应该了解 malloc 是如何工作的:
调用 malloc 时,它会检查分配器是否具有请求大小的内存。glibc
如果是这样,它会将此内存返回给您。此内存通常是由于先前的操作而产生的,因此在大多数情况下,它具有垃圾值(可能为零或不为零)。free
另一方面,如果它找不到内存,它将要求操作系统通过调用或系统调用为其分配内存。
出于安全原因,OS 返回一个零初始化页面,因为此内存可能已被另一个进程使用,并携带有价值的信息,例如密码或个人数据。sbrk
mmap
您可以从此链接中自行阅读:
相邻的块可以自由合并,无论它们是什么 尺寸是。这使得该实现适用于各种 分配模式通常不会造成高内存浪费 通过碎片化。
非常大的块(比页面大得多)是用 mmap 分配的 (匿名或通过 /dev/zero)通过此实现
在某些实现中,使用 OS 的此属性,并要求 OS 为其分配页,以确保内存始终为零初始化,而无需初始化它本身。calloc
评论
malloc
malloc
的结果