You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何calloc分配新内存块而非复用tcache中的内存?

问题:calloc为何未复用tcache中的内存块?

代码示例

以下是一段C代码:

#include <stdio.h>
#include <stdlib.h>

int main()
{
        void *ptr;
        ptr = malloc(8);
        printf("First malloc: %p\n", ptr);
        free(ptr);

        ptr = malloc(8);
        printf("Second malloc: %p\n", ptr);
        free(ptr);

        ptr = calloc(1, 8);
        printf("Using calloc: %p\n", ptr);
        free(ptr);

        return 0;
}

运行现象

这段代码连续三次在堆上分配并释放8字节内存,运行后输出如下:

First malloc: 0x560cceb282a0
Second malloc: 0x560cceb282a0
Using calloc: 0x560cceb286d0

可见前两次malloc复用了同一地址,但calloc使用了不同地址。

GDB调试过程

使用带GEF的GDB进一步排查:

  1. 第一次free后查看tcache:
gef➤  b 10
gef➤  r
gef➤  heap bins
─ Tcachebins for thread 1 ─
Tcachebins[idx=0, size=0x20] count=1  ←  Chunk(addr=0x5555555592a0, size=0x20, flags=PREV_INUSE) 

tcache bins中存在一个大小为0x20的内存块,理论上应被同大小的后续分配复用。

  1. 第二次malloc后验证复用:
gef➤  n
gef➤  heap bins
─ Tcachebins for thread 1 ─
All tcachebins are empty
gef➤  p ptr
$1 = (void *) 0x5555555592a0

malloc如预期复用了tcache中的内存块。

  1. 第二次free后,内存块回到tcache:
gef➤  n
gef➤  n
gef➤  heap bins
─ Tcachebins for thread 1 ─
Tcachebins[idx=0, size=0x20] count=1  ←  Chunk(addr=0x5555555592a0, size=0x20, flags=PREV_INUSE) 
  1. 但calloc分配后,tcache未被使用:
gef➤  n
gef➤  heap bins
─ Tcachebins for thread 1 ─
Tcachebins[idx=0, size=0x20] count=1  ←  Chunk(addr=0x5555555592a0, size=0x20, flags=PREV_INUSE) 
gef➤  p ptr
$2 = (void *) 0x5555555596d0

疑问

为何calloc调用未使用tcache bins中的内存?

环境信息

编译命令、gcc及libc版本:

$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
$ gcc -o exe src.c -g
$ ldd --version
ldd (Ubuntu GLIBC 2.31-0ubuntu9.16) 2.31

Linux系统信息:

$ uname -a
Linux pierre-laptop 5.15.0-124-generic #134~20.04.1-Ubuntu SMP Tue Oct 1 15:27:33 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

原因分析

你使用的GLIBC 2.31版本中,calloc的实现逻辑和malloc存在差异:

  1. calloc的核心要求是返回已清零的内存,但tcache中存储的是free后的脏数据(默认不会自动清零)。
  2. 在GLIBC 2.31及更早版本中,calloc为了保证语义正确性,默认会跳过tcache的内存块,优先从堆中分配新内存并清零,或者从fast bin、unsorted bin等其他区域获取内存后再清零。
  3. 直到GLIBC 2.34版本,才调整了calloc的逻辑,允许复用tcache中的内存块,但会在返回前对其执行清零操作,以此平衡性能和语义要求。

简言之,当前使用的GLIBC版本里,calloc为了确保返回内存清零,默认不复用tcache中的脏数据块。

内容的提问来源于stack exchange,提问作者Pierre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 06:55:02