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

时间:2011-11-06 19:06:00

标签: c linux gcc malloc

可能从平台到平台不同,但

当我使用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也会产生不必要的性能开销?


编辑:哦,我之前的例子不是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)

但感谢您指出在进行mallocing时有一个安全原因! (没想过)。当分配新块或大块时,它必须初始化为零。

10 个答案:

答案 0 :(得分:168)

简答:

它没有,它恰好在你的情况下为零。
(你的测试用例也没有显示数据为零。它只显示一个元素是否为零。)


长答案:

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

  1. 它会回收先前已分配并从同一进程中释放的内存。
  2. 它从操作系统请求新页面。
  3. 在第一种情况下,内存将包含先前分配的剩余数据。所以它不会是零。这是执行小额分配时的常见情况。

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

    以下是捕获:来自操作系统的内存将因安全原因而归零。*

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

    *我注意到C标准对此没有任何说明。这严格来说就是操作系统行为。因此,在不考虑安全性的系统上,这种归零可能存在也可能不存在。


    为此提供更多性能背景:

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


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

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

    我见过的最极端的例子是使用48 GB暂存缓冲区进行70秒操作的20秒归零开销。 (大约30%的开销。) (当然:机器确实缺少内存带宽。)

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

答案 1 :(得分:21)

操作系统通常会清除它发送给您的进程的新内存页面,因此无法查看旧进程的数据。这意味着第一次初始化一个变量(或malloc的东西)时,它通常会为零,但是如果你重复使用那个内存(例如,通过释放它并再次进行mallocing),那么所有的赌注都会关闭。

这种不一致正是为什么未初始化的变量难以找到的原因。


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

答案 2 :(得分:17)

为什么假设malloc()初始化为零?恰好是第一次调用malloc()会导致调用sbrkmmap系统调用,这会从操作系统分配一页内存。出于安全原因,操作系统必须提供零初始化内存(否则,来自其他进程的数据变得可见!)。所以你可能会想到 - 操作系统浪费时间归零页面。但不是!在Linux中,有一个特殊的系统范围的单例页面,称为“零页面”&#39;并且该页面将被映射为Copy-On-Write,这意味着只有当您实际在该页面上写入时,操作系统才会分配另一个页面并对其进行初始化。所以我希望这能回答你关于表现的问题。内存分页模型通过支持同一页面的多个映射功能以及在第一次写入发生时处理大小写的能力,允许内存的使用是惰性的。

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

请注意,glibc上的malloc() 手册页严格说明内存未被清除,因此&#34;合同&#34;在API上,你不能认为它确实被清除了。这是最初的摘录:

  

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

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

答案 3 :(得分:14)

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

答案 4 :(得分:3)

来自gnu.org

  

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

答案 5 :(得分:2)

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

答案 6 :(得分:2)

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

从不永远指望任何编译器生成将内存初始化为任何内容的代码。 malloc只返回一个指向n字节内存的指针 someplace 它甚至可能在swap中。

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

答案 9 :(得分:0)

malloc 不会将内存初始化为零。它会按原样返回给您,而不会触及内存或更改其值。

那么,为什么我们会得到这些零?

在回答这个问题之前,我们应该了解ma​​lloc是如何工作的

当您调用 malloc 时,它会检查 glibc 分配器是否具有请求大小的内存。

如果可以,它会将这段记忆返还给您。此内存通常是由于先前的 free 操作而产生的,因此在大多数情况下它具有垃圾值(可能为零或不为零)。

另一方面,如果找不到内存,它会要求操作系统为其分配内存,调用sbrk或{ {1}} 个系统调用。 出于安全原因,操作系统会返回一个初始化为零的页面,因为该内存可能已被另一个进程使用并携带诸如密码或个人数据等有价值的信息。

你可以从这个Link中自己阅读它:

<块引用>

相邻的块可以在空闲时合并,无论它们是什么 大小是。这使得该实现适用于各种 分配模式通常不会导致大量内存浪费 通过碎片化。

<块引用>

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

在某些实现中 mmap uses this property of the OS 并要求操作系统为其分配页面以确保内存始终为零初始化而不初始化它本身。