2011-11-06 127 views
71

也许它是从平台到平台不同的,但是为什么malloc在gcc中将值初始化为0?

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

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

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

为什么malloc的行为就像这样,即使有calloc?

这是否意味着即使您不希望它有时会将值初始化为0,也会产生不必要的性能开销?编辑:哦,我以前的例子不是开始,但恰巧使用“新鲜”块。

我正是一直在寻找是为什么当它分配一个大块它初始化:

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) 

但感谢指出有mallocing当安全理由! (从来没有想过)。当分配新块或大块时,必须将其初始化为零。

+12

为了更真实的测试,你尝试过分配,释放,然后重新分配(可能每个多次重复)?仅仅因为malloc第一次返回零初始化的内存并不意味着你可以一般地指望它。 – user786653

+1

它也可能是内存被操作系统设置为0,或者'malloc'与它无关。 –

+0

[出于安全原因,不要在C中输入'malloc'的结果(http://stackoverflow.com/q/605845/995714) –

回答

157

答案很简单:

它不,它只是发生在你的情况下为零。
(也测试用例不显示数据是零,仅显示了如果一个元素是零。)


龙答:

当你调用malloc(),两个一事情将发生:

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

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

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

这里的问题:来自操作系统的内存将归零为安全的原因。*

当操作系统给你的内存,它可能是从一个不同的进程中解脱出来。所以内存可能包含密码等敏感信息。因此,为了防止您读取这些数据,操作系统在将它提供给您之前将其清零。

*我注意到,C标准没有提到这一点。这完全是一种操作系统行为。因此,这种调零可能会或可能不会出现在不担心安全问题的系统上。


为了让更多的性能背景这一点:

由于@R。在评论中提到,这个调零是为什么你应该总是use calloc() instead of malloc() + memset()calloc()可以利用这个事实来避免单独的memset()


另一方面,这种调零有时是性能瓶颈。在某些数值应用程序(例如out-of-place FFT)中,您需要分配大量的暂存内存。用它来执行任何算法,然后释放它。

在这些情况下,调零是不必要的,相当于纯开销。

最极端的例子我所看到的是用于与48 GB临时缓冲区一个70第二操作20秒回零开销。 (大约30%的开销) (授予:机器也有一个缺少的存储器带宽。)

显而易见的解决方案是简单地手动重新使用存储器。但是这通常需要突破已建立的界面。 (尤其是如果它是库例程的一部分)

+18

+1。 –

+14

但是你仍然不能指望它是零,除非你自己这样做(或者使用'calloc',这是从OS获得内存后为你做的)。 –

+0

感谢您的回答。 mallocing时从未想过会有安全问题! – SHH

0

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

2

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

2

您的代码没有证明malloc将其内存初始化为0.这可以在程序启动之前由操作系统完成。为了看到这种情况,向内存写入一个不同的值,释放它,然后再次调用malloc。你可能会得到相同的地址,但你必须检查。如果是这样,你可以看看它包含什么。让我们知道!

20

操作系统通常会明确记忆犹新页面发送给你的过程,因此不能看的旧进程的数据。这意味着你第一次初始化一个变量(或者malloc的东西)时,它通常会是零,但是如果你重复使用了这个内存(比如释放它和再次malloc),那么所有的赌注都是关闭的。

这种不一致就是为什么未初始化的变量很难找到这样的错误。


对于不需要的性能方面的开销,避免不确定的行为可能是更重要的。无论在这种情况下可以获得的小性能提升都不会弥补难以找到的错误,如果有人稍微修改代码(破坏以前的假设)或将其移植到另一个系统(假设可能无效首先)。

+3

+1 ...不知道“可能”在粗体文本中是必需的;-) – 2011-11-06 19:25:42

15

为什么你认为malloc()初始化为零?只是碰巧第一次拨打电话malloc()导致致电sbrkmmap系统调用,系统调用从OS分配一页内存。由于安全原因,操作系统必须提供零初始化内存(否则,其他进程的数据可见!)。所以你可能会认为 - 操作系统浪费时间将页面归零。但不是!在Linux中,有一个特殊的系统范围的单独页面被称为“零页面”,该页面将被映射为写入时复制,这意味着只有当你真正在该页面上写入时,操作系统才会分配另一页面,初始化它。所以我希望这能回答你关于表现的问题。内存分页模型允许通过支持同一页面的多重映射能力以及在第一次写入时处理事件的能力来缓存内存。

如果调用free(),该glibc分配器将区域返回到其空闲列表,当malloc()再次调用,你可能会得到同样的区域,但脏以前的数据。最后,free()可能会通过再次调用系统调用将内存返回到操作系统。

注意,glibc手册页malloc()严格的说,内存不被清除,因此通过对API的“契约”,你不能想当然地认为它得到清除。这里是原始摘录:

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

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

0

从不有史以来指望任何编译器生成的代码将初始化内存任何东西。 malloc只是返回一个指向n个字节的内存某处它可能甚至会交换。

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

+4

除了在语言保证它将被初始化的情况下。没有显式初始化的静态对象被隐式地初始化为零。 –

2

gnu.org

非常大的块(比页面更大)被分配使用mmap(匿名或通过的/ dev /零)此实现。

+0

尽管OP在小步骤中正在运行。你发现的这个参考文献是否也有关于此的内容? – hugomg

13

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

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