【问题标题】:Why memset called after calloc?为什么 memset 在 calloc 之后调用?
【发布时间】:2019-02-07 13:52:20
【问题描述】:

我研究了一些库的代码,并注意到对calloc 的调用后跟memset 用于calloc 分配的块。 我发现这个问题对callocmalloc + memset 之间的差异有相当全面的回答,并在分配存储之前调用memset

Why malloc+memset is slower than calloc?

我仍然不明白为什么要这样做。 这种操作有什么好处?

上述库中的代码示例:

light_pcapng_file_info *light_create_default_file_info()
{
    light_pcapng_file_info *default_file_info = calloc(1, sizeof(light_pcapng_file_info));
    memset(default_file_info, 0, sizeof(light_pcapng_file_info));
    default_file_info->major_version = 1;
    return default_file_info;
}

分配结构的代码(每个数组包含32个元素):

typedef struct _light_pcapng_file_info {
    uint16_t major_version;
    uint16_t minor_version;
    char *file_comment;
    size_t file_comment_size;
    char *hardware_desc;
    size_t hardware_desc_size;
    char *os_desc;
    size_t os_desc_size;
    char *user_app_desc;
    size_t user_app_desc_size;
    size_t interface_block_count;
    uint16_t link_types[MAX_SUPPORTED_INTERFACE_BLOCKS];
    double timestamp_resolution[MAX_SUPPORTED_INTERFACE_BLOCKS];

} light_pcapng_file_info;

编辑:

除了接受的答案之外,我还想提供一些我的同事指出的信息。 glibc 中有一个错误,有时会阻止 calloc 将内存清零。这是链接: https://bugzilla.redhat.com/show_bug.cgi?id=1293976

链接被移动时的实际错误报告文本:

glibc: calloc() 返回非零内存

问题描述:

在 Facebook,我们有一个应用程序在从 glibc-2.12-1.149.el6.x86_64 转换到 glibc-2.12-1.163.el6.x86_64 时开始奇怪地挂起和崩溃。原来是这个补丁

glibc-rh1066724.patch

介绍了问题。

您在 _int_malloc() 中添加了以下位

  /* There are no usable arenas.  Fall back to sysmalloc to get a chunk from
     mmap.  */
  if (__glibc_unlikely (av == NULL))
    {
      void *p = sYSMALLOc (nb, av);
      if (p != NULL)
       alloc_perturb (p, bytes);
      return p;
    }

但这不行,alloc_perturb 无条件地将前字节设置为 0xf,这与上游检查是否设置了 perturb_byte 不同。这需要改成

if (p != NULL && && __builtin_expect(perturb_byte, 0))
   alloc_perturb (p, bytes);
return p;

我附加的补丁解决了我的问题。

这个问题由于竞技场上的任何类型的锁争用导致我们退回到 mmap()'ing 一个新块这一事实而加剧。这是因为我们检查我们检查的无人竞争的竞技场是否损坏,如果是,我们循环通过,如果我们循环到开头,我们知道我们没有找到任何东西。除非我们最初的 arena 实际上没有损坏,否则我们仍然会返回 NULL,所以我们更频繁地使用这个 mmap() 东西,这确实让事情变得不稳定。

请尽快解决这个问题,我什至认为这是一个可能的安全问题。

【问题讨论】:

  • calloc 已经 0 初始化内存,因此使用 memset 再次执行它是没有意义的(除非您正在处理有缺陷的 calloc 实现)。
  • 可能库作者一开始没有使用calloc,而是malloc?也许图书馆的作者并不真正知道calloc 应该做什么?也许还有无数其他原因?能回答的只有原作者。我们能做的就是猜测
  • 代码是某个菜鸟写的。就是这样……没什么好考虑的。
  • 阅读其他代码并从中学习很重要,但它确实有一个丑陋的缺点:那里有很多写得非常糟糕的代码,这只是一个小例子。在 calloc 之后调用 memset 绝对没有意义。

标签: c malloc calloc memset


【解决方案1】:

调用memset() 可确保操作系统实际执行虚拟内存映射。正如您链接的问题的答案中所述,calloc() 可以进行优化,以便延迟实际的内存映射。

应用程序可能有理由推迟虚拟内存映射的实际创建 - 例如使用缓冲区从非常高速的设备中读取,尽管在使用 @987654323 的情况下@ 将内存清零,使用calloc() 而不是malloc() 似乎是多余的。

【讨论】:

  • 不,这不能解释原因。您只需触摸一个内存单元即可实现这一目标 - 您不必为了强制分配而将整个分配的内存归零。这将是不必要的低效。
  • @Lundin 不应该是每页一个内存单元需要被触摸吗?在 OP 帖子的示例中,它可能是单个页面(甚至是页面的一部分),但如果内存跨越多个页面并且页面大小未知,那么 memset 调用可能会更简单。
  • 无论如何,如果目的是强制分配,我肯定会期待一些解释这就是目的的评论。
  • 请注意,gcc 8.2.1calloc() 之后很乐意将memset() 优化为零,因此这种对地址进行预故障的尝试将被理智的编译器无论如何 破坏。
  • @Lundin 但“代码是由某个菜鸟编写的。” ;-)
【解决方案2】:

没有人是完美的,仅此而已。是的,memsetcalloc 之后归零是奢侈的。如果您想要一个显式的memset 以保证您拥有您所要求的内存,那么它应该跟在malloc 之后。

新代码每 1,000 行大约有 20 个错误,概率法则意味着并非所有错误都被清除。另外,这并不是一个真正的错误,因为没有可观察到的不良行为。

【讨论】:

    【解决方案3】:

    我会称它为错误,因为它正在做毫无意义的工作,calloc() 被指定返回已清除的内存,那么为什么要再次清除它呢?当有人从 malloc() 切换时,也许重构失败。

    如果代码是开源的和/或在您有权访问的存储库中,我会检查这些行的提交历史记录,看看发生了什么。运气好的话,有一条提交消息告诉我们动机......

    【讨论】:

    • 这并非完全没有意义——调用memset() 可确保实际创建了虚拟内存页面。虽然如果这是代码的原因,malloc() 然后memset() 会是更好的选择。
    • 正如comments on another answer 中已经提到的,现代编译器将 malloc+memset(0) 优化为 calloc,这可能会从操作系统获得未触及的零页面。 (即使使用归零循环也可能发生这种情况:编译器还知道如何将循环优化为 memset 调用,例如利用当前 CPU 的手动调整 asm,然后应用 malloc+memset(0) 优化。)
    猜你喜欢
    • 2012-05-28
    • 2013-03-06
    • 2013-11-17
    • 2014-06-15
    • 2017-12-25
    • 2016-05-14
    • 2018-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多