为何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进一步排查:
- 第一次
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的内存块,理论上应被同大小的后续分配复用。
- 第二次
malloc后验证复用:
gef➤ n gef➤ heap bins ─ Tcachebins for thread 1 ─ All tcachebins are empty gef➤ p ptr $1 = (void *) 0x5555555592a0
malloc如预期复用了tcache中的内存块。
- 第二次
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)
- 但
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存在差异:
calloc的核心要求是返回已清零的内存,但tcache中存储的是free后的脏数据(默认不会自动清零)。- 在GLIBC 2.31及更早版本中,
calloc为了保证语义正确性,默认会跳过tcache的内存块,优先从堆中分配新内存并清零,或者从fast bin、unsorted bin等其他区域获取内存后再清零。 - 直到GLIBC 2.34版本,才调整了
calloc的逻辑,允许复用tcache中的内存块,但会在返回前对其执行清零操作,以此平衡性能和语义要求。
简言之,当前使用的GLIBC版本里,calloc为了确保返回内存清零,默认不复用tcache中的脏数据块。
内容的提问来源于stack exchange,提问作者Pierre
相关产品推荐
相关产品推荐

