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

Why does malloc initialize the values to 0 in gcc?

提问人:SHH 提问时间:11/7/2011 最后编辑:sigjuiceSHH 更新时间:12/12/2021 访问量:51247

问:

也许它因平台而异,但是

当我使用 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)

但感谢您指出,定位错误时存在安全原因!(从来没想过)。当然,在分配新块或大块时,它必须初始化为零。

C Linux GCC malloc

评论

15赞 user786653 11/7/2011
对于更真实的测试,您是否尝试过分配、释放然后再次分配(可能多次重复)?仅仅因为 malloc 第一次返回零初始化内存并不意味着您通常可以指望它。
3赞 Seth Carnegie 11/7/2011
也可能是内存作系统或其他东西设置为 0,与它无关。malloc
1赞 phuclv 10/16/2017
不要在 C 中强制转换 malloc 的结果

答:

0赞 user23743 11/7/2011 #1

你知道它肯定正在初始化吗?malloc() 返回的区域有没有可能一开始就经常有 0?

2赞 user418748 11/7/2011 #2

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

195赞 Mysticial 11/7/2011 #3

简短的回答:

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


长答案:

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

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

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

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

这里有一个问题:出于安全原因,来自操作系统的内存将被清零。

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

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


为了给大家更多的性能背景:

正如 @R. 在评论中提到的,这种归零就是为什么你应该始终使用 calloc() 而不是 malloc() + memset()。 可以利用这一事实来避免单独的.calloc()memset()


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

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

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

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

评论

14赞 Greg Hewgill 11/7/2011
但是你仍然不能指望它是零,除非你自己这样做(或者用 ,在从操作系统获取内存后为你做这件事)。calloc
0赞 SHH 11/7/2011
谢谢你的回答。从没想过在定位时会出现安全问题!
34赞 Mysticial 11/7/2011
这很微妙。当操作系统为您提供内存时,它本可以从不同的进程中释放出来。因此,内存可能包含敏感信息,例如密码。因此,为了防止您读取此类数据,操作系统会在将其提供给您之前将其归零。但这是一个实现细节,可能会有所不同,例如在某些嵌入式系统中。
21赞 R.. GitHub STOP HELPING ICE 11/7/2011
这与 OP 的问题有点无关紧要,但这种影响的一个后果是,当您想要零初始化内存时,您应该始终使用而不是 +(至少对于时间归零可能很重要的大块)。+ 将始终产生写入整个块的沉重成本,但系统可以利用新的匿名内存一开始就为零填充的事实。callocmallocmemsetmallocmemsetcalloc
4赞 thomasrutter 11/7/2011
这个问题的答案可能会帮助你理解这一点。内核可以通过 calloc 作弊,因为在使用它们之前不会真正写出所有清零的页面。Memset(显然)强制立即写出页面。更多信息请见链接。
2赞 TonyK 11/7/2011 #4

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

22赞 hugomg 11/7/2011 #5

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

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


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

评论

4赞 11/7/2011
+1 ...不确定粗体文字中是否需要“可能”;-)
20赞 Dan Aloni 11/7/2011 #6

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

如果调用 ,分配器会将该区域返回到其可用列表,当再次调用时,您可能会获得相同的区域,但与以前的数据相差无几。最终,可能会通过再次调用系统调用将内存返回到操作系统。free()glibcmalloc()free()

请注意,手册严格说内存没有被清除,所以通过API上的“契约”,你不能假设它确实被清除了。以下是原始摘录:glibcmalloc()

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

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

0赞 FlyingGuy 11/7/2011 #7

永远不要指望任何编译器生成将内存初始化为任何内容的代码。malloc 只是返回一个指向 n 字节内存的指针,它甚至可能处于交换状态。

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

评论

5赞 Keith Thompson 12/30/2011
除非语言保证它将被初始化。没有显式初始化的静态对象将隐式初始化为零。
4赞 TomaszK 11/7/2011 #8

gnu.org

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

评论

0赞 hugomg 11/7/2011
不过,OP 正在以小步进行调整。你找到的那个参考资料也有这方面的内容吗?
16赞 Praetorian 11/7/2011 #9

我修改了您的示例以包含 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赞 yoyo_fun 9/7/2017
我尝试了你所做的,如果我只分配 100 个字节,那么即使指针指向 SAME 地址,该地址的值也不同。如果我分配 400 个字节或更多,则指针值和内存中的值是相同的。你认为可能是什么原因?
1赞 Mostafa Wael 4/8/2021 #10

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

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

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

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

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

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

您可以从此链接中自行阅读:

相邻的块可以自由合并,无论它们是什么 尺寸是。这使得该实现适用于各种 分配模式通常不会造成高内存浪费 通过碎片化。

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

在某些实现中,使用 OS 的此属性,并要求 OS 为其分配页,以确保内存始终为零初始化,而无需初始化它本身。calloc