【问题标题】:Security/performance implications of malloc over calloc?malloc 对 calloc 的安全/性能影响?
【发布时间】:2014-10-16 17:47:14
【问题描述】:

根据Linux手册页,callocmalloc的区别在于malloc不初始化内存,而calloc初始化内存。

这在实践中意味着如果我创建一个这样的结构:

struct Danger {
    char a;
    char b;
    char c;
    char d;
}

如果我用malloc 创建它:

struct Danger *dang = malloc(sizeof(struct Danger));

似乎dang->a 在这一点上真的可以是任何值,因为内存没有初始化。如果我使用calloc:

struct Danger *dang = calloc(1, sizeof(struct Danger));

我现在知道dang->a 必须等于\0

malloc 可能存在的原因可能是您将立即写入已分配的整个内存空间,而您一开始并不真正关心那里的内容。这为您节省了一个额外的步骤,即清零无论如何都会被覆盖的数据

不过,从程序安全性和稳定性的角度来看,使用malloc 应该是例外而不是规则,因为它很容易导致未定义的行为。

两个问题:

  • C 程序是否经常因malloc 而处理意外功能?这是一个相当频繁的问题吗?
  • 如果我每次都使用calloc 而不是malloc,我真的会受到多少性能损失?

【问题讨论】:

  • 为什么需要一些内存来存储数据是一种特殊情况?例如,您是否曾经打开并阅读过文件?或者从网络套接字读取?还是转换了字符串编码?还是加密了一些数据?
  • 为了继续长篇大论,我们可以扭转这个局面:你什么时候需要最初为零的东西? 应该是例外。零只是众多合理数字中的一个,它可能是算法中某些状态的初始值。
  • @PascalCuoq:我不认为 heartbleed 是 malloc 暴露旧数据的一个例子。 Heartbleed 是读取缓冲区末尾并跨入堆上其他(可能已释放,可能仍分配)内存的问题。
  • 在许多情况下(对于大分配),无论如何你都会得到匿名mmap 的结果,而这在某些系统(例如 Linux)上往往是零初始化的。对于您的问题 1:如果您使用未初始化的值,任何好的静态分析器、带有警告的编译器或类似 valgrind 的东西都会对您大喊大叫。因此,如果您使用此类工具并修复警告,这不是什么大问题。
  • 一个相反的观点是calloc 会让静态分析器和 Valgrind 看起来像你打算分配块的初始值为零,如果该值没有意义并且您忘记了用合理的值真正初始化它,他们将无法警告您您在程序中引入的功能错误。使用malloc,静态分析器和 Valgrind 会知道你的意图并警告你忘记初始化(部分)块。

标签: c performance security memory-management


【解决方案1】:

C 程序是否经常处理由于 malloc 导致的意外功能?这是一个相当频繁的问题吗?

他们经常处理“问题”,但通常会尽量不读取垃圾数据。这通常没有问题,因为无论如何都需要初始化数据。真正的危险在于无意中读取了垃圾/零数据,使用calloc()无法排除这种情况。

有一些方法可以确保程序不会读取垃圾数据。最简单的方法是使用valgrind 执行测试套件。它报告垃圾数据的任何读数。它还将捕获任何 use-after-free 错误、未分配内存的访问、丢失的内存块等。

如果我每次只使用calloc 而不是malloc,我真的会受到多少性能损失?

正是写入两次写入数据的开销。它可能会从缓存中删除一些有价值的数据,因此它实际上取决于内存块有多大,以及您之后使用的操作顺序来填充有意义的数据。

  • 最坏的情况:归零会从缓存中驱逐需要填充内存块的数据。这些数据需要从内存中重新加载,这是性能影响的主要部分。对于大约几兆字节的内存块,这可能相当于大约 100 微秒的影响。细节取决于您的机器。

  • 最佳情况:您在分配一个小内存块后立即填充它而不读取任何数据。在这种情况下,开销只是将数据写入一级缓存的开销。即使这是一个快速操作,您仍然可以期望归零与您自己的初始化花费相同的时间 - 缓存并不是无限快的。


个人而言,当我需要零初始化块时,我只使用calloc() 作为获得零初始化块的便捷方式。通常情况并非如此。但它有时会发生。

【讨论】:

  • +1 提到了 valgrind,允许 valgrind 之类的工具检测未初始化的使用是使用 malloc 而不是 calloc 的巨大优势。当 calloc 写入内存时,它不能再推断在没有另一个初始化的情况下使用这个值是有意的还是无意的。在没有 valgrind 的情况下,如果零不是一个好值,则很难调试未初始化的使用。 glibc malloc 还为您提供 MALLOC_PERTURB_ 以更轻松地找到这些问题。
  • Calloc 也比 malloc 更好更快,并且手动将内存归零以获得非常大的内存。
  • @jclin calloc()malloc() & memset() 的性能可能具有欺骗性,因为 calloc() 可以简单地将真正的归零推迟到以后。 stackoverflow.com/questions/2688466/… 将内存归零的净时间可能会更长,但它并不总是立即显示出来。
  • @jclin 如果您的内存分配是由内核请求的新内存支持的,那么您是对的:内核将应用程序请求的所有新页面归零以防止侧通道攻击。 calloc() 知道这一点,在这种情况下应该跳过归零。但是,此逻辑不适用于重用内存。典型的内存分配器永远不会将内存返回给内核。他们自己重用它。因此,calloc() 的性能提升是一次性的。净收益仅限于将进程的整个内存占用归零一次的时间,在大多数情况下不到一秒。
猜你喜欢
  • 2012-08-22
  • 1970-01-01
  • 1970-01-01
  • 2016-08-27
  • 2013-08-30
  • 2021-03-09
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多