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

Ubuntu虚拟机中C语言free()函数的堆内存疑问

堆内存free()行为解析

测试程序

int main(int argc, char **argv) {
    char *b1, *b2, *b3, *b4, *b_large;
    b1 = malloc(8);
    memset(b1, 0xaa, 8);
    b2= malloc(16);
    memset(b2, 0xbb, 16);
    b3 = malloc(25);
    memset(b3, 0xcc, 25);
    b4= malloc(1000);
    memset(b4, 0xdd, 1000);
    free(b1);
    free(b2);
    free(b3);
    free(b4);
}

第一次调用free()前的内存情况(gdb输出)

(gdb) x/20gx  0x555555559290 
0x555555559290: 0x0000000000000000  0x0000000000000021
0x5555555592a0: 0xaaaaaaaaaaaaaaaa  0x0000000000000000
0x5555555592b0: 0x0000000000000000  0x0000000000000021
0x5555555592c0: 0xbbbbbbbbbbbbbbbb  0xbbbbbbbbbbbbbbbb
0x5555555592d0: 0x0000000000000000  0x0000000000000031
0x5555555592e0: 0xcccccccccccccccc  0xcccccccccccccccc
0x5555555592f0: 0xcccccccccccccccc  0x00000000000000cc
0x555555559300: 0x0000000000000000  0x00000000000003f1
0x555555559310: 0xdddddddddddddddd  0xdddddddddddddddd
0x555555559320: 0xdddddddddddddddd  0xdddddddddddddddd

第一次调用free()后的内存情况(gdb输出)

(gdb) x/20gx  0x555555559290 
0x555555559290: 0x0000000000000000  0x0000000000000021
0x5555555592a0: 0x0000000555555559  0xd13e7903c502febc
0x5555555592b0: 0x0000000000000000  0x0000000000000021
0x5555555592c0: 0xbbbbbbbbbbbbbbbb  0xbbbbbbbbbbbbbbbb
0x5555555592d0: 0x0000000000000000  0x0000000000000031
0x5555555592e0: 0xcccccccccccccccc  0xcccccccccccccccc
0x5555555592f0: 0xcccccccccccccccc  0x00000000000000cc
0x555555559300: 0x0000000000000000  0x00000000000003f1
0x555555559310: 0xdddddddddddddddd  0xdddddddddddddddd
0x555555559320: 0xdddddddddddddddd  0xdddddddddddddddd

疑问与解析

疑问

我原本预期在内存第二行能看到可读的前后指针,第三行的两个8字节段均为0x20。请问free()函数为何会有这样的行为?

解析

这是glibc内存分配器(ptmalloc2)中**tcache(线程缓存)**机制导致的行为,具体原因如下:

  1. tcache的定位:从glibc 2.26版本开始,tcache默认启用,它是线程本地的小尺寸chunk缓存,目的是加速小内存块的分配/释放,减少多线程场景下的锁竞争。你分配的b1是8字节,属于tcache管理的尺寸范围(默认上限为64字节)。

  2. free后chunk的存储方式:当你调用free(b1)时,这个chunk不会进入通用空闲链表(如unsorted bin、small bin),而是被放入tcache对应大小的单链表中。此时tcache会复用原用户数据区域来存储链表指针和安全随机值:

    • 原b1用户数据起始地址0x5555555592a0的前8字节是tcache链表的fd指针,指向链表中下一个同大小的空闲chunk(这里是首次释放同大小chunk,指针值对应tcache控制结构的地址,小端存储后显示为0x0000000555555559);
    • 第二个8字节的0xd13e7903c502febc是glibc加入的随机填充值,用于破坏攻击者对堆布局的预期,提升安全性。
  3. 为何不符合你的预期:

    • 你预期的"前后指针"是通用空闲链表的双向链表结构,但tcache使用单链表,仅需fd指针,且不会修改chunk头部的prev_size和size字段,因此看不到双向链表的bk指针;
    • 第三行0x5555555592b0是b2 chunk的头部(prev_size和size字段),b2的size值为0x21——其中低3位是标志位,0x1表示PREV_INUSE(前一个chunk处于使用中),实际chunk大小为0x20。由于tcache中的chunk不会与相邻chunk合并,b2的PREV_INUSE标志保持为1,因此size字段依然是0x21,不会变为0x20。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 00:24:51