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

SHH*_*SHH 76 c linux malloc gcc

也许从平台到平台不同,但是

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

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

int main()
{
    double *a = (double*) malloc(sizeof(double)*100)
    printf("%f", *a);
}
Run Code Online (Sandbox Code Playgroud)

为什么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)
Run Code Online (Sandbox Code Playgroud)

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

Mys*_*ial 170

简答:

它没有,它恰好在你的情况下为零.
(此外,您的测试用例并未显示数据为零.它仅显示一个元素是否为零.)


答案很长:

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

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

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

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

这是一个问题:出于安全原因,来自操作系统的内存将归零.*

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

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


为此提供更多的表现背景:

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


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

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

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

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

  • 这很微妙.当操作系统为您提供内存时,它可能已从不同的进程中释放出来.因此内存可能包含敏感信息,如密码.因此,为了防止您读取此类数据,操作系统会在将其提供给您之前对其进行归零.但它是一个实现细节,可能会有所不同,例如在某些嵌入式系统中. (34认同)
  • 这与OP的问题有点不同,但这种效果的一个结果是,当你想要零初始化内存时,你应该总是使用`calloc`而不是`malloc` +`memset`(至少对于时间较长的大块零可能很重要).`malloc` +`memset`总是会导致写入整个块的成本很高,但系统的`calloc`可以利用新的匿名内存将从零开始填充的事实. (21认同)
  • 但是你仍然*不能指望它为零,除非你自己这样做(或者使用`calloc`,它在从操作系统获取内存后为你做). (14认同)
  • [这个问题](http://stackoverflow.com/questions/2688466/why-mallocmemset-slower-than-calloc)中的答案可以帮助您理解这一点.内核可以使用calloc作弊,因为在使用之前实际上并未写出所有已归零的页面.Memset(显然)强制页面立即写出.链接中的更多信息. (3认同)

hug*_*omg 21

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

这种不一致正是为什么未初始化的变量是如此难以发现的错误.


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

  • +1 ...不确定粗体文字中是否需要"可能";-) (4认同)

Dan*_*oni 18

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

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

请注意,glibc 手册页malloc()严格说明内存未被清除,因此通过API上的"合同",您不能认为它已被清除.这是最初的摘录:

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

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


Pra*_*ian 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;
}
Run Code Online (Sandbox Code Playgroud)

用gcc 4.3.4输出

100.000000
100.000000
Run Code Online (Sandbox Code Playgroud)