【问题标题】:assuming uninitialized heap memory is initialized to zero when using mmap?假设使用 mmap 时未初始化的堆内存被初始化为零?
【发布时间】:2021-01-14 20:44:39
【问题描述】:

我们知道虽然.bss 内存位置(例如未初始化的全局C 变量)总是由加载器初始化为零,但堆内存并非如此。一个常见的错误是假设堆内存被初始化为零。

但是mmap获得的分配内存呢?我们可以通过调用mmap来请求堆内存,将堆中的一个区域映射到一个匿名文件(需求零页),那么在这种情况下我们可以安全地假设堆内存初始化为零?

而且,malloc 实际上在内部调用mmap 来请求内存用于大尺寸的分配,而对于小尺寸的分配,malloc 仍然在内部调用sbrk/brk。对于后者,我们当然不能假设堆内存被初始化为零。

所以当malloc 在内部实际调用mmap 时,我们仍然可以假设堆内存初始化为零?我知道这仍然不是一个好的做法,我们不应该在所有情况下都阅读假设 0,但我只是想确保我的理论是正确的,以便我知道我对 mmap 的理解是正确的。

【问题讨论】:

  • 如果你想保证内存设置为零,我建议你使用calloc
  • 我没有资格回答这个问题,但不要假设任何没有记录的内容。
  • 谁说“malloc 内部仍然调用 sbrk/brk”?对于某些特定的实现可能是这样。
  • 如果未初始化的变量进入.bss,那么它们被零初始化。 如果。这是一个有效的编译器选择,因为您可以覆盖这些零,而 C 仅保证未初始化的变量是可写的。你不能假设未初始化的变量是可读的,更不用说值为零了。
  • @MSalters 6.9.2/2:“具有文件范围的对象的标识符声明,没有初始化程序,没有存储类说明符或存储类说明符static , 构成一个暂定定义。如果一个翻译单元包含一个或多个标识符的暂定定义,并且该翻译单元不包含该标识符的外部定义,则行为与翻译单元包含该标识符的文件范围声明,复合类型截至翻译单元的末尾,初始值设定项等于 0。"?

标签: c heap-memory virtual-memory


【解决方案1】:

你的假设是错误的。在 Linux 上,mmap 有一个选项 MAP_UNINITIALIZEDmalloc 可用于更快的分配。

【讨论】:

  • 感谢您简洁的回答。你能告诉我malloc在哪里调用mmapMAP_UNINITIALIZED吗,我检查了几次源代码,仍然找不到它
  • @slowjams: 源代码?您不能假设特定的 malloc 实现是世界上唯一的实现。微软malloc 甚至不调用mmap
  • @slowjams 你检查了哪个源代码?没有 malloc 实现,因此如果您希望代码可移植,请不要依赖未记录的实现细节。
  • @MSalters 这样我可以在哪里查看malloc 的代码?我用的是 Linux
  • @slowjams uClibc 在其“no-mmu”系统的malloc 实现中使用MAP_UNINITIALIZEDgithub.com/kraj/uClibc/blob/master/libc/stdlib/malloc-simple/…
【解决方案2】:

我们知道,虽然 .bss 内存位置(例如未初始化的全局 C 变量)总是被加载器初始化为零...

我不知道 - 可能是加载程序,可能是链接到程序中的语言启动代码(例如“crt0.o”),可能是提供“新页面归零”保证的操作系统/内核在哪里loader 不必费心。

但是mmap得到的分配内存呢?

谁的mmap()怎么用的?

对于 POSIX;该规范不包括直接用于分配内存的mmap()。相反,您应该为打开的“内存对象”(例如来自“posix_typed_mem_open()”)获取文件描述符,然后使用其文件描述符映射内存对象;并且用于创建“内存对象”的函数的规范大多只是说(非常解释)“废话,实现定义,废话,抱歉我们的规范直到为时已晚才出现”。因此,对于该区域是否归零没有什么可说的。

对于 Linux,mmap(..., MAP_ANONYMOUS) 为您提供零页面,mmap(..., MAP_ANONYMOUS | MAP_UNINITIALIZED) 可能不会。

对于 Windows,您不使用 mmap() 分配内存 - 您使用 VirtualAlloc() 代替,这可以保证新内存归零。

其他所有操作系统都可以做任何其他想做的事情。

一般;多任务操作系统必须担心安全问题(例如,仍在由一个进程释放的内存中的数据在由另一个进程分配后可以访问)并且出于安全原因(和方便)将填充页面(用零)。

所以对于malloc内部实际调用mmap的大尺寸分配,我们仍然可以假设堆内存初始化为零?

你可以假设任何你喜欢的,直到你发现假设是错误的。这包括假设malloc() 在内部调用mmap()(在某些条件下或在任何条件下)。例如,C 库可以在程序的.bss 中保留大量空间并将其用于malloc(),因此malloc() 永远不会使用mmap()sbrk()(或VirtualAlloc() 或.. .) 完全没有。

如果您确实知道malloc() 从某个地方获得了底层内存,该内存可以保证内存被零填充(这几乎是不可能的,因为您可以获得旧内存和新内存的混合);那么你仍然不能假设malloc()(或语言运行时环境的任何其他部分——启动代码、C 库等)没有暂时将内存用于某些元数据,并且仍然不能假设从malloc() 分配的内存在你得到它时仍然是零。

请注意,堆可能会优化calloc(),因此如果它知道内存已经用零填充(因为它保持跟踪,并且还知道底层内存最初来自提供保证的某个地方)它不会无缘无故地用零填充内存。本质上,calloc() 是“有保证的malloc()”(而不是“malloc() 后跟memset()”)。

【讨论】:

  • malloc 在 Windows 上可以很好地使用 HeapAlloc 而不会传递 HEAP_ZERO_MEMORY。所以在 Windows 上你也没有保证。然后可以优化calloc 以使用HEAP_ZERO_MEMORY 调用HeapAlloc
猜你喜欢
  • 1970-01-01
  • 2021-11-11
  • 2014-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-18
  • 2022-09-27
  • 2016-02-20
相关资源
最近更新 更多