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(线程缓存)**机制导致的行为,具体原因如下:
tcache的定位:从glibc 2.26版本开始,tcache默认启用,它是线程本地的小尺寸chunk缓存,目的是加速小内存块的分配/释放,减少多线程场景下的锁竞争。你分配的
b1是8字节,属于tcache管理的尺寸范围(默认上限为64字节)。free后chunk的存储方式:当你调用
free(b1)时,这个chunk不会进入通用空闲链表(如unsorted bin、small bin),而是被放入tcache对应大小的单链表中。此时tcache会复用原用户数据区域来存储链表指针和安全随机值:- 原
b1用户数据起始地址0x5555555592a0的前8字节是tcache链表的fd指针,指向链表中下一个同大小的空闲chunk(这里是首次释放同大小chunk,指针值对应tcache控制结构的地址,小端存储后显示为0x0000000555555559); - 第二个8字节的
0xd13e7903c502febc是glibc加入的随机填充值,用于破坏攻击者对堆布局的预期,提升安全性。
- 原
为何不符合你的预期:
- 你预期的"前后指针"是通用空闲链表的双向链表结构,但tcache使用单链表,仅需fd指针,且不会修改chunk头部的
prev_size和size字段,因此看不到双向链表的bk指针; - 第三行
0x5555555592b0是b2chunk的头部(prev_size和size字段),b2的size值为0x21——其中低3位是标志位,0x1表示PREV_INUSE(前一个chunk处于使用中),实际chunk大小为0x20。由于tcache中的chunk不会与相邻chunk合并,b2的PREV_INUSE标志保持为1,因此size字段依然是0x21,不会变为0x20。
- 你预期的"前后指针"是通用空闲链表的双向链表结构,但tcache使用单链表,仅需fd指针,且不会修改chunk头部的
内容的提问来源于stack exchange,提问作者rdre8
相关产品推荐
相关产品推荐

