为什么malloc在gcc中将值初始化为0?


77

平台之间可能有所不同,但是

当我使用gcc编译并运行下面的代码时,每次在ubuntu 11.10中获得0。

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

int main()
{
    double *a = (double*) malloc(sizeof(double)*100)
    printf("%f", *a);
}

为什么即使有calloc,malloc的行为也是如此?

难道就意味着即使您不希望有时将值初始化为0,也会有不必要的性能开销吗?


编辑:哦,我以前的示例不是initiazling,而是碰巧使用“新鲜”块。

我恰好在寻找的是为什么它在分配一个大块时将其初始化:

int main()
{
    int *a = (int*) malloc(sizeof(int)*200000);
    a[10] = 3;
    printf("%d", *(a+10));

    free(a);

    a = (double*) malloc(sizeof(double)*200000);
    printf("%d", *(a+10));
}

OUTPUT: 3
        0 (initialized)

但是感谢您指出在分配时有一个安全原因!(从未考虑过)。确保在分配新鲜块或大块时必须将其初始化为零。


13
为了进行更实际的测试,您是否尝试过分配,释放然后再次分配(可能重复多次)?仅仅因为malloc第一次返回零初始化的内存并不意味着您通常可以依靠它。
2011年

1
也可能是因为操作系统或其他原因将内存设置为0,而与之malloc无关。
赛斯·卡内基

Answers:


177

简短答案:

并非如此,在您的情况下恰好为零。
(此外,您的测试用例不会显示数据为零。它只会显示一个元素为零的情况。)


长答案:

当您致电时malloc(),将发生以下两种情况之一:

  1. 它回收先前分配并从同一进程中释放的内存。
  2. 它从操作系统请求新页面。

在第一种情况下,内存将包含先前分配剩余的数据。因此它不会为零。在执行小分配时,这是通常的情况。

在第二种情况下,内存将来自操作系统。当程序内存不足时,或者当您请求很大的分配时,就会发生这种情况。(例如您的示例)

问题在于:出于安全原因,来自操作系统的内存将被清零。*

当操作系统为您提供内存时,它可能已从其他进程中释放出来。这样,内存可以包含敏感信息,例如密码。因此,为防止您读取此类数据,操作系统会在将数据提供给您之前将其清零。

*我注意到C标准对此没有说明。严格来说,这是一种操作系统行为。因此,在不考虑安全性的系统上,可能会或可能不会出现这种调零。


要为此提供更多的性能背景:

作为@R。在评论中提到,此归零是为什么您应始终使用calloc()而不是malloc()+的原因memset()calloc()可以利用这个事实来避免分开memset()


另一方面,这种归零有时是性能瓶颈。在某些数字应用程序中(例如,不适当的FFT),您需要分配大量的暂存存储器。用它执行任何算法,然后释放它。

在这些情况下,归零是不必要的,并且完全是开销。

我看到的最极端的示例是使用48 GB暂存缓冲区进行70秒的操作需要20秒的清零开销。(大约30%的开销。) (保证:计算机确实缺少内存带宽。)

显而易见的解决方案是简单地手动重用内存。但这通常需要突破已建立的接口。(特别是如果它是库例程的一部分)


14
但是您仍然不能指望它为零,除非您自己这样做(或使用calloc,从操作系统获取内存后再为您完成)。
格雷格·希吉尔

感谢您的回答。从来没有想过在分配时会出现安全问题!
SHH

34
真微妙 当操作系统为您提供内存时,它可能已从其他进程中释放出来。这样,内存可以包含敏感信息,例如密码。因此,为防止您读取此类数据,操作系统会在将数据提供给您之前将其清零。但这是一个实现细节,并且可能有所不同,例如在某些嵌入式系统中。
Mysticial 2011年

21
除了OP的问题外,这还有点余地,但是这种影响的结果是,当您需要零初始化的内存时(至少对于时间为零的大块而言,应该),应该始终使用calloc而不是malloc+ memsetmalloc+memset总是会导致写入整个块的开销很大,但是系统calloc可以利用以下事实:新的匿名内存开始时将为零。
R .. GitHub停止帮助ICE,

4
这个问题的答案可能有助于您理解。内核可以通过在未使用所有被调零的页面之前不写出所有被调零的页面,从而用calloc作弊。Memset(显然)强制立即将页面写出。链接中的更多信息。
thomasrutter 2011年

21

操作系统通常会清除发送给您进程的新内存页面,因此它无法查看较旧进程的数据。这意味着,第一次初始化变量(或malloc某个东西)时,它通常为零,但是如果您重用了该内存(例如,通过释放它并再次malloc-ing),则所有选择都将关闭。

这种不一致正是未初始化变量如此难以发现错误的原因。


至于不必要的性能开销,避免未指定的行为可能更重要。如果有人稍微修改了代码(违反了先前的假设)或将其移植到另一个系统(假设可能无效),那么在这种情况下您获得的任何小小的性能提升都将无法弥补很难找到的bug。首先)。


4
+1 ...不知道粗体文本思想中是否需要“可能” ;-)

18

为什么假设malloc()初始化为零?碰巧的是,第一个调用会malloc()导致对sbrk或的调用mmap系统调用,它从OS分配一页内存。出于安全原因,操作系统必须提供零初始化内存(否则,其他进程的数据将变得可见!)。因此,您可能会认为-操作系统浪费时间将页面清零。但不是!在Linux中,有一个特殊的系统级单例页面称为“零页面”,该页面将映射为“写时复制”,这意味着只有当您实际在该页面上书写时,操作系统才会分配另一个页面,并且初始化它。因此,我希望这能回答您有关性能的问题。内存分页模型通过支持同一页的多个映射功能以及处理首次写入发生时的情况的能力,可以使内存的使用变得很懒。

如果调用free()glibc分配器会将区域返回到其空闲列表,malloc()再次调用时,您可能会得到相同的区域,但会弄脏先前的数据。最终,free()可能会通过再次调用系统调用将内存返回给OS。

请注意,glibc 手册页上的malloc()严格说明未清除内存,因此通过API上的“合同”,您不能假定已清除内存。这是原始摘录:

malloc()分配大小字节,并返回指向已分配内存的指针。
内存未清除。如果size为0,则malloc()返回NULL或唯一的指针值,该值以后可以成功传递给free()。

如果您愿意,可以在担心性能或其他副作用的情况下阅读该文档的更多信息。


15

我修改了您的示例以包含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

我尝试了您所做的事情,如果我只分配了100个字节,那么即使指针指向SAME地址,该地址的值也不同。如果我分配400个字节或更多,则指针值和内存中的值都相同。您认为可能是什么原因?
yoyo_fun

3

来自gnu.org

此实现通过mmap(匿名或通过/ dev / zero)分配了非常大的块(比页面大得多)。


不过,OP正在小步进行分配。您找到的参考资料是否也与此有关?
hugomg

2

该标准没有规定malloc()应将值初始化为零。它只是在您的平台上发生,它可能被设置为零,或者在您读取该值的特定时刻它可能为零。


2

您的代码未演示malloc将其内存初始化为0。这可以由操作系统在程序启动之前完成。要查看是否存在shich,请将一个不同的值写入内存,释放它,然后再次调用malloc。您可能会获得相同的地址,但是您必须进行检查。如果是这样,您可以查看其中包含的内容。让我们知道!


0

您知道它肯定会初始化吗?由malloc()返回的区域是否可能经常在开始时就有0?


0

从来没有指望任何编译器生成的代码,将初始化的内存来什么。malloc只是在某个地方返回一个指向n个字节的内存的指针甚至。

如果内存的内容很重要,请自行初始化。


4
该语言保证将其初始化的情况除外。没有显式初始化的静态对象将隐式初始化为零。
基思·汤普森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.