【问题标题】:Why does malloc initialize the values to 0 in gcc?为什么 malloc 将 gcc 中的值初始化为 0?
【发布时间】:2011-12-23 04:41:26
【问题描述】:

可能平台不同,但是

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

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

【问题讨论】:

  • 为了更真实的测试,您是否尝试过分配、释放然后再次分配(可能每次都重复多次)?仅仅因为 malloc 第一次返回零初始化内存并不意味着您可以指望它。
  • 也有可能是内存被操作系统设置为0什么的和malloc无关。

标签: c linux gcc malloc


【解决方案1】:

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

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

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

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

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

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

您可以通过Link 自行阅读:

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

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

在某些实现中,callocuses this property of the OS 并要求操作系统为其分配页面以确保内存始终为零初始化,而无需自行初始化。

【讨论】:

    【解决方案2】:

    简答:

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


    长答案:

    当您拨打malloc() 时,会发生以下两种情况之一:

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

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

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

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

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

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


    为了提供更多的性能背景:

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


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

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

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

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

    【讨论】:

    • 但是你仍然不能指望它为零,除非你自己这样做(或使用calloc,它会在从操作系统获取内存后为你做) .
    • 感谢您的回答。没想到mallocing时会出现安全问题!
    • 这很微妙。当操作系统为您提供内存时,它可能已从不同的进程中释放出来。因此,该内存可能包含敏感信息,例如密码。因此,为了防止您读取此类数据,操作系统会在将其提供给您之前将其归零。但这是一个实现细节,可能会有所不同,例如在某些嵌入式系统中。
    • 这有点与 OP 的问题无关,但这种影响的一个结果是,当您想要零初始化内存时,您应该始终使用 calloc 而不是 malloc+memset (至少对于时间归零可能很重要的大块)。 malloc+memset 总是会导致写入整个块的高昂成本,但系统的calloc 可以利用新的匿名内存一开始就会被零填充的事实。
    • this question 中的答案可能会帮助您理解这一点。内核可以通过在使用之前不实际写出所有清零页面来欺骗 calloc。 Memset(显然)强制立即写出页面。更多信息见链接。
    【解决方案3】:

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

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

    注意malloc() 上的glibc 手册页 严格说内存没有被清除,所以根据API 上的“合同”,您不能假设它确实被清除了。以下是原文摘录:

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

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

    【讨论】:

      【解决方案4】:

      我修改了您的示例以包含 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 个字节,那么即使指针指向相同的地址,该地址的值也是不同的。如果我分配 400 字节或更多,那么指针值和内存中的值都是相同的。您认为可能是什么原因?
      【解决方案5】:

      来自gnu.org

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

      【讨论】:

      • 不过,OP 正在逐步进行分配。您找到的参考文献是否也有相关内容?
      【解决方案6】:

      永远永远指望 any 编译器生成将内存初始化为任何内容的代码。 malloc 只是返回一个指向 n 字节内存的指针某个地方它甚至可能在交换中。

      如果内存的内容很关键,请自行初始化。

      【讨论】:

      • 除非语言保证它会被初始化。没有显式初始化的静态对象被隐式初始化为零。
      【解决方案7】:

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

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


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

      【讨论】:

      • +1 ...不确定在粗体文本中是否需要“可能”;-)
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

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

        【讨论】:

          【解决方案10】:

          你知道它肯定被初始化了吗? malloc() 返回的区域是否有可能只是频繁地以0开头?

          【讨论】:

            猜你喜欢
            • 2019-12-23
            • 2013-08-07
            • 1970-01-01
            • 1970-01-01
            • 2021-10-16
            • 2011-01-10
            • 2021-01-12
            相关资源
            最近更新 更多