开篇故事:一个负数的内存统计
一个常驻服务上线后,有人在监控面板上看到「当前堆占用」是-1072950624 字节。第一反应是打点代码写错了,查下来没有——它只是老老实实地打印mallinfo().uordblks。程序当时持有约 3 GiB 堆内存,int字段装不下 3 GiB,绕回了负数。更危险的是反向情况:如果没人盯着看,一个"看起来很小甚至为负"的数字会让人误以为没有泄漏,而进程正在悄悄吃掉整台机器的内存。
这个程序后来把mallinfo()换成了mallinfo2(),问题消失。但这引出三个更基本的问题:
mallinfo()返回的十个字段,各自到底在数什么?这些数字是怎么算出来的?- 它为什么长这样——一个 1980 年代标准定义的接口,为什么会出现在 2026 年的代码库里?
- 拿它做内存泄漏排查,怎么判断一条曲线是"泄漏"还是"正常缓存"?
本文用五个可复现的小程序逐一实测回答。所有数字来自一台 Linux 5.15(WSL2,x86_64,glibc 2.41),完整输出见result.txt,源码、Makefile与这份输出都收在文末的资源包里,make result可复现。原理两节直接对照 glibc 2.41 的malloc/malloc.c与malloc/arena.c源码,并给出与 man page 不一致处的实测裁决。换一台机器,绝对数字会变,不变的是字段口径和判定方法——涉及耗时的几处另附重复运行的抖动范围。
1. 先说结论
mallinfo() | mallinfo2() | |
|---|---|---|
| 字段类型 | int | size_t |
| 引入版本 | SVID2/XPG 时代的老接口 | glibc2.33(2021 年 2 月) |
| 堆 > 2 GiB | 溢出,打出负数 | 正确 |
| 编译告警 | glibc 2.33 起标记deprecated,-Wall会警告 | 无 |
| 格式化 | %d | %zu |
新代码直接写mallinfo2();老代码看到负数或编译告警,就是该迁移的信号。下面两节先讲清楚它的出身和原理,再回到字段和用法。
2. 从哪来:一个 Unix 标准留下的接口
mallinfo()不是 glibc 的发明,它是一个被标准化出来的接口。glibc 的malloc/malloc.c开头有一段 1990 年代写下的注释,原文是:
This version of malloc supports the standard SVID/XPG mallinfo routine that returns a struct containing usage properties and statistics. It should work on any SVID/XPG compliant system that has a /usr/include/malloc.h defining struct mallinfo.翻译过来:这个 malloc 实现要支持SVID/XPG 规定的标准 mallinfo 例程,任何符合 SVID/XPG 的系统都应该有/usr/include/malloc.h里的struct mallinfo。
- SVID(System V Interface Definition,AT&T 的系统 V 接口定义)和XPG(X/Open Portability Guide,X/Open 组的可移植性指南)是上世纪八九十年代 Unix 标准化竞赛的产物。两家都想规定"一个合格的 Unix C 库该长什么样","能报告堆用量"就是其中一条。
- glibc 的 malloc 系(Gloger 的 ptmalloc,源自 Doug Lea 的 dlmalloc)为了满足这份规范实现了它,接口原封不动地活到了今天。你在 glibc 源码里读到的是一段 30 年前的接口约定,这不是巧合。
标准把结构体规定死了,于是字段和实现对不上
标准定义的struct mallinfo字段是按老版 System V malloc 的内部结构写的,跟 ptmalloc 的实现并不匹配。glibc 注释自己也承认:
The main declaration needed is the mallinfo struct that is returned (by-copy) by mallinfo(). The SVID/XPG malloinfo struct contains a bunch of fields that are not even meaningful in this version of malloc. These fields are are instead filled by mallinfo() with other numbers that might be of interest.(malloinfo、are are和malloc. These之间的双空格都是源码原样,这里没有替 glibc 修字。)
「一堆字段在这个 malloc 里根本没意义,我们只好拿别的数字填进去」。最好的证据是usmblks——malloc.h里它的注释是:
intusmblks;/* always 0, preserved for backwards compatibility */man page 补充了它的前世:这个字段历史上是"历史峰值"(highwater mark),只在单线程时代维护过,多线程化之后没人再更新它,于是恒为 0,但字段必须留着,因为标准里有它。
int字段同样是那个年代的遗产:接口定型时,堆超过 2 GiB 在 32 位机器上根本不可想象。这个债欠了 30 年,直到 2021 年才还——glibc 2.33 的NEWS里有这么两条(原文分列于该版两条独立条目,这里放在一起):
* The mallinfo2 function is added to report statistics as per mallinfo, but with larger field widths to accurately report values that are larger than fit in an integer. * The mallinfo function is marked deprecated. Callers should call mallinfo2 instead.顺带一提,mallinfo2的结构体在malloc.h里挂的注释仍然是SVID2/XPG mallinfo2 structure which can handle allocations bigger than 4GB——补一个标准框架下的新版,而不是另起炉灶,这是对"接口被标准锁死"最直白的应对。
小结这一节:mallinfo()存在的理由是兼容一个 1980 年代的 Unix 标准;它的字段语义是标准和实现妥协的产物;它的int类型是 32 位时代的遗产;而mallinfo2()是 glibc 给这份遗产做的第一次正面修复。
3. 背景:glibc 的堆长什么样
要理解字段和后面的遍历逻辑,先得知道 glibc 从系统拿内存的两条路:
- 主堆(sbrk):向后连续扩展的那块。小块分配(默认
< 128 KiB,即M_MMAP_THRESHOLD)都从这里切。 - mmap 独立映射:大块分配(
>= 128 KiB)直接mmap一段独立区域,free时立刻还给内核。
主堆内部再分:正在使用的 chunk、fastbin 里的空闲 chunk、普通 bin 里的空闲 chunk、以及堆顶还没切的 top chunk。glibc 还支持多 arena:多线程下每个线程可以从main_arena头部分裂出自己的 arena(malloc/arena.c把新 arena 头插进main_arena.next的环形链表),避免所有线程抢一把锁。第 4.3 节会看到,mallinfo的实现就是沿这条环走的。
结构与字段的对应关系:
系统 ├─ sbrk ──→ 主堆 main_arena ─┬─ 在用 chunk ────→ uordblks │ ├─ fastbin 空闲 ──→ fsmblks (smblks 个) │ ├─ bin 空闲 ─────→ fordblks 的一部分 (ordblks 个) │ └─ top chunk ────→ keepcost 是它的可释放部分 ├─ sbrk/mmap ─→ 线程 arena 1..N(同样有上面四类,都计入 arena/uordblks) └─ mmap ──────→ 独立映射区 ──────────────────→ hblks (个数) / hblkhd (字节)4. 原理:这些数字是现算出来的
4.1 现场推导,不是记账
最容易误解的一点:mallinfo()背后没有任何统计子系统。glibc 不会"每次 malloc 时 +1"——malloc 为了完成本职工作,本来就维护着 free list、top chunk 和每个 arena 的system_mem(该 arena 从系统要了多少字节)。mallinfo做的只是在被调用的那一刻,把这些现成的数据结构走一遍、加总、返回副本。
这带来两个直接推论:
- 平时零成本——不调用就不遍历,没有记账开销。
- 调用有成本,成本正比于要遍历的结构大小(见 4.5 节实测),而不是分配次数。
4.2 一次调用的完整过程(glibc 2.41 源码)
核心是malloc/malloc.c里的int_mallinfo(mstate av, struct mallinfo2 *m),对单个 arena先把 top chunk 记为「空闲」,再走两条空闲链:
/* Account for top */avail=chunksize(av->top);/* 先把 top chunk 算作"空闲" */nblocks=1;/* traverse fastbins */nfastblocks=0;fastavail=0;for(i=0;i<NFASTBINS;++i)/* NFASTBINS:x86_64 上 10 条 */for(p=fastbin(av,i);p!=NULL;p=REVEAL_PTR(p->fd)){++nfastblocks;fastavail+=chunksize(p);}avail+=fastavail;/* traverse regular bins */for(i=1;i<NBINS;++i)/* NBINS=128,0 号不存在,遍历其余 127 条 */{b=bin_at(av,i);for(p=last(b);p!=b;p=p->bk){++nblocks;avail+=chunksize(p);}}m->smblks+=nfastblocks;/* fastbin 空闲块另计 smblks */m->ordblks+=nblocks;/* 空闲块个数 = top + 普通 bin */m->fordblks+=avail;/* 空闲字节数 = top + fastbin + bin */m->uordblks+=av->system_mem-avail;/* 在用 = 从系统拿的 - 空闲的 */m->arena+=av->system_mem;m->fsmblks+=fastavail;if(av==&main_arena){/* 只访问主 arena 时填一次: */m->hblks=mp_.n_mmaps;/* 全局 mmap 计数(进程级) */m->hblkhd=mp_.mmapped_mem;m->usmblks=0;/* "历史峰值"字段就是这一行清零的 */m->keepcost=chunksize(av->top);/* 堆顶可 trim 的量 */}REVEAL_PTR(p->fd)不是绕路:glibc 2.32 起 fastbin 的单向表用了 safe-linking,fd存进去时被PROTECT_PTR用「本地址右移 12 位再异或」打散过,读链必须先还原,照老代码写p->fd拿到的是异或值。这一行也是本文引的源码片段里最容易照抄错的一处。
三个值得注意的设计:
uordblks = system_mem - avail:在用字节不是逐个分配累加的,而是"从系统拿的总量减去能数出来的空闲"。所以 fastbin 之外那些"已 free 但还在 tcache 里"之类的边角料,只要没进 bin,都会被算成在用。- mmap 数字来自全局
mp_结构,进程级共享,所以hblks/hblkhd天然是全进程口径——但它只数带 IS_MMAPPED 标记的分配,非主 arena 的堆段虽然底层也是 mmap 来的,却计入arena而不是hblkhd(4.3 节实测会看到)。 arena、uordblks是把每个 arena 的贡献累加的,因此是全进程口径——前提是遍历真的覆盖了所有 arena,这正是下一节要实测裁决的问题。
4.3 多 arena:环形遍历 + 逐个加锁
外层的__libc_mallinfo2()长这样:
memset(&m,0,sizeof(m));ar_ptr=&main_arena;do{__libc_lock_lock(ar_ptr->mutex);/* 锁住这个 arena */int_mallinfo(ar_ptr,&m);/* 累加它的贡献 */__libc_lock_unlock(ar_ptr->mutex);/* 解锁,再去下一个 */ar_ptr=ar_ptr->next;/* 沿环形链表前进 */}while(ar_ptr!=&main_arena);/* 绕回起点为止 */returnm;malloc/arena.c创建新 arena 时执行a->next = main_arena.next; main_arena.next = a;——新 arena 被头插进这条环。所以从源码看,遍历会覆盖所有 arena。
但 man page 不这么写。man 3 mallinfo2的 BUGS 一节明明白白地说:
Information is returned for only the main memory allocation area. Allocations in other arenas are excluded.「只统计主分配区,其他 arena 的分配被排除」。应是 arena 机制出现之前留下的文本,与今天的源码矛盾。信谁?实测。demo_arena.c起 4 个线程,每线程在屏障同步后同时分配 8 MiB(全部低于 mmap 阈值,走 arena),从主线程对账:
before : arena=0 uordblks=0 (0.0 MiB) during : arena=33722368 uordblks=33580192 (32.0 MiB) delta : arena=+33722368 uordblks=+33580192 (32.0 MiB) [期望 +32 MiB] --- malloc_stats()(逐 arena 打印,同一套 int_mallinfo)--- Arena 0: system bytes = 135168 in use bytes = 5920 Arena 1: system bytes = 8396800 in use bytes = 8393568 Arena 2: system bytes = 8396800 in use bytes = 8393568 Arena 3: system bytes = 8396800 in use bytes = 8393568 Arena 4: system bytes = 8396800 in use bytes = 8393568裁决:4 个线程的内存全部计入了。主线程读到的增量 33580192 字节 = 33554432(4×8 MiB 负载)+ 25760(chunk/heap 元数据开销),一个字节都不缺;malloc_stats顺带证明这些内存确实躺在 Arena 1…4,而不是碰巧被谁算进了主 arena。glibc 2.41 上 man page 那句 BUGS 是过时的。实测中还冒出一个有意思的现象:Arena 1..4的system bytes各有 8 MiB,但max mmap regions = 0——这些堆段底层明明是mmap出来的,却全部记在arena字段里,hblkhd一个字节都没涨,印证了 4.2 节说的"只数带 IS_MMAPPED 标记的分配"。
顺带说清快照的性质:每个 arena 在自己被访问的那一刻是加锁读取的,内部一致;但 5 个 arena 是逐个访问的,别的线程可以在你读完 Arena 0 之后、读 Arena 1 之前改 Arena 0——所以它不是一个全局原子快照。同理,加锁意味着这个函数不能在信号处理器里调用(可能死锁)。
4.4mallinfo()=mallinfo2()+ 逐字段截断
有了上面的铺垫,mallinfo()的实现只有一层皮:
structmallinfo__libc_mallinfo(void){structmallinfom;structmallinfo2m2=__libc_mallinfo2();/* 先按 size_t 算全 */m.arena=m2.arena;/* 再逐字段赋给 int */m.ordblks=m2.ordblks;...returnm;}溢出就发生在m.arena = m2.arena;这一行:size_t赋给int,超出int范围时按实现惯用的二补码截断取低 32 位。没有报错、没有饱和、没有警告——第 6 节的实测会把这个回绕算术验算到底。
这也说明两版的统计口径完全一致(毕竟同一个函数算的),差异只在装结果的容器大小。
4.5 成本:O(空闲块数),不是 O(分配数)
既然每次调用都现场遍历,成本是多少?demo_cost.c测三种堆状态下的单次调用耗时(各 200 次取平均):
(a) 初始(空闲 chunk 极少) : 0.1 us/call (b) 持有 100 万个在用块 uordblks=76 MiB: 0.1 us/call (c) 全部释放 fordblks=76 MiB : 6457.0 us/call© 这个绝对值是抖动的:同一台机器重复跑,落在 5.4~6.5 毫秒都正常(打印精度 0.1 µs,(a)/(b) 两行在 0.0 和 0.1 之间跳)。要看的是两件事:
- (b) vs ©:同一个 76 MiB 的堆,块一个都没多没少,只是从"在用"变成"已释放"——耗时从 0.1 µs 量级涨到约 6 毫秒(约 6 ns/空闲块)。空闲块才是成本的唯一来源。
- (a) vs (b):从几乎空的堆到 100 万个在用块,耗时纹丝不动(0.1 µs)——在用块根本不被遍历,印证 4.2 节的源码。
实践推论:正常服务的 free list 很短,mallinfo2()是亚微秒级的廉价调用,随便采;但如果你的堆里躺着几十万个空闲块(典型场景:长期运行、大量不同尺寸的分配释放),一次调用要持有 arena 锁好几毫秒——这时高频轮询它会和分配线程互相拖累。它也不是 async-signal-safe 的。
5. 字段逐个看:十个里面盯三个
#include<malloc.h>structmallinfomi=mallinfo();/* 或 mallinfo2(),字段同名 */| 字段 | 含义 | 值得看吗 |
|---|---|---|
arena | 所有 arena 从系统拿到的总字节(含在用 + 空闲) | 看趋势 |
ordblks | 空闲块个数(top + 普通 bin;fastbin 另计smblks) | 辅助 |
smblks | fastbin 空闲块个数 | 少用 |
hblks | mmap 独立映射区个数 | 看分配粒度 |
hblkhd | mmap 独立映射区总字节 | 看(大块分配全在这里) |
usmblks | 恒为 0(单线程时代的"历史峰值",见第二节) | 忽略 |
fsmblks | fastbin 空闲块总字节 | 辅助 |
uordblks | 当前在用的总字节(= Σ arena 的 system_mem − 空闲) | 最常用 |
fordblks | 当前空闲块总字节 | 看(区分"用了"和"没还") |
keepcost | 堆顶可通过malloc_trim归还的最大字节(仅 main_arena) | 解释 RSS 时用 |
日常盯三个就够:uordblks(真在用)、fordblks(空闲但还在进程手里)、hblkhd(mmap 大块)。近似关系:arena ≈ uordblks + fordblks(加 chunk 开销)。
实测:分配、释放、再看 mmap 路径
demo_basic.c四次采样(节选,单位字节):
=== 初始状态 === arena=0 uordblks=0 fordblks=0 hblks=0 hblkhd=0 === 64x64KiB 分配并释放后(堆已扩张,空闲块留在 fordblks) === arena=135168 uordblks=4768 fordblks=130400 hblks=0 hblkhd=0 === 再持有 8x1MiB 后(看 hblks/hblkhd 涨、arena 不动) === arena=135168 uordblks=4768 fordblks=130400 hblks=8 hblkhd=8421376 === 释放 8x1MiB 后(hblkhd 归零:mmap 内存真正还给内核) === arena=135168 uordblks=4768 fordblks=130400 hblks=0 hblkhd=0三个值得停下来的地方:
- 64 次 64 KiB 的"分配+释放"结束后,
uordblks只剩 4768,但arena是 135168。内存还给了 free list(fordblks=130400),没还给操作系统。这就是"空闲 ≠ RSS 下降"的实证。 - 8×1 MiB 全部持有时,
uordblks一动不动,涨的是hblkhd。mmap 大块不计入uordblks——监控只盯uordblks的话,会完全看不见这部分内存。要看全貌得uordblks + hblkhd。 - 释放 8×1 MiB 后
hblkhd立刻归零。mmap 路径是"即借即还",所以大对象频繁分配释放时,RSS 曲线会很干净。
6. mallinfo2():那个 -1 GiB 是怎么来的
两版结构体字段一一对应,唯一区别是类型(intvssize_t)。3 GiB 的堆塞进 32 位int必然回绕,4.4 节已经看到了回绕发生的位置。demo_overflow.c把它做成了可见的:每块 64 KiB(低于 mmap 阈值,全部进主堆),分配 3 GiB 后同时打印两个接口。程序每块只触碰首字节,所以真实 RSS 远小于 3 GiB,普通机器也跑得动——回绕取决于堆的账目,跟物理内存无关。
分配 3072 MiB ... mallinfo() (int): arena = -1072881664 (-1.00 GiB,负数即溢出) uordblks = -1072950624 (-1.00 GiB) fordblks = 68960 mallinfo2() (size_t): arena = 3222085632 (3.00 GiB) uordblks = 3222016672 (3.00 GiB) fordblks = 68960验算回绕:3222016672 - 2^32 = -1072950624,与打印值完全一致——就是把size_t的低 32 位当成了int。
注意两个细节:
fordblks都是 68960,没溢出。空闲块很小,所以老程序里往往"一部分字段看着正常、一部分是负数",更容易骗过粗略的检查。- glibc 直接在头文件里给
mallinfo()标了__MALLOC_DEPRECATED,-Wall编译即警告(result.txt开头记录了这两条):
warning: ‘mallinfo’ is deprecated [-Wdeprecated-declarations] 19 | struct mallinfo mi = mallinfo();这不是第三方库的建议,是 libc 自己在说"别用这个了"。
迁移是机械替换
-structmallinfomi=mallinfo();-printf("%d\n",mi.uordblks);+structmallinfo2mi=mallinfo2();+printf("%zu\n",mi.uordblks);字段名完全一样,只需改类型名、函数名和格式符(%d→%zu)。唯一的兼容性代价是老 glibc(< 2.33,如 CentOS 7 的 2.17)没有mallinfo2。需要兼容时:
#ifdefined(__GLIBC__)&&(__GLIBC__>2||(__GLIBC__==2&&__GLIBC_MINOR__>=33))structmallinfo2mi=mallinfo2();printf("used=%zu\n",mi.uordblks);#elsestructmallinfomi=mallinfo();printf("used=%d\n",mi.uordblks);#endif7. 实践:用 uordblks 曲线区分泄漏和缓存
只看一个瞬时数字没有意义,看趋势。demo_leak.c跑 20 轮"分配 1000 块 4 KiB、全部释放"的循环,唯一的区别是leak模式每轮丢掉一个指针(每轮泄漏 4 KiB):
### demo_leak stable 0 4 +0 1 4 +0 ... 19 4 +0 ← 平坦 ### demo_leak leak 0 8 +4 1 12 +4 2 16 +4 ... 19 84 +4 ← 每轮 +4 KiB,斜率精确等于泄漏量判定规则很简单:
- 平坦或锯齿:正常。锯齿来自 glibc 自己的 free list 复用,峰谷差就是工作集大小。
- 单调爬升,斜率不收敛:泄漏。而且斜率就是泄漏速率——上面每轮 +4 KiB,和丢掉的块大小完全一致,说明
uordblks的增量可以直接当泄漏量用。
上线时把它接成埋点:每隔 N 秒记一次uordblks、hblkhd、fordblks三个值。只涨不跌且fordblks不同步上涨,才是泄漏;如果uordblks平稳而fordblks/arena涨,那只是 free list 变大,是缓存不是泄漏。采样频率参考 4.5 节:free list 很短时随便采,怀疑空闲块很多时先看ordblks再定频率。
8. 陷阱清单
- 不是全局原子快照。逐 arena 加锁读取,每个 arena 内部一致,跨 arena 不保证同一时刻(见 4.3 节);也不能在信号处理器里调用(拿锁可能死锁)。至于 man page BUGS 那句"只统计主 arena",2.41 实测已被证伪——但也别赌老版本行为,见 FAQ。
- 口径有限。只统计 malloc 族的分配。C++ 的
new在 glibc 上最终走 malloc 所以可见,但自定义 allocator、静态区、栈都不算。它更不是 RSS——arena里"空闲"的部分可能仍占着物理页。hblkhd只数 IS_MMAPPED 分配,线程 arena 的 mmap 堆段记在arena里。 - 空闲 ≠ 归还 OS。第 5 节实测:64 次 64 KiB 分配释放完,
uordblks回落到 4768 字节,但arena仍停在 135168——大头留在 free list(fordblks=130400)。主堆只在满足条件时通过malloc_trim/M_TRIM_THRESHOLD(默认 128 KiB)向 OS 收缩,keepcost就是"最多还能 trim 掉多少"。mmap 大块则不同,free即归零(hblkhd实测可见)。 - 可移植性:仅 glibc。musl、macOS(BSD malloc)、Windows 都没有这两个函数,
<malloc.h>本身也不是标准头。跨平台代码要包起来:
#ifdefined(__GLIBC__)#include<malloc.h>#defineHAVE_MALLINFO1#endif- 换 allocator 后失真。程序若用
LD_PRELOAD换成 jemalloc/tcmalloc,或者编译期替换,glibc 的mallinfo统计不到它们的分配(tcmalloc 有自己的MallocExtension接口)。看到数字"不涨"先确认底下是谁在管内存。 - 空闲块巨大时调用变贵。100 万个空闲块 ≈ 6 ms/次且持锁(4.5 节实测),高频轮询会拖累分配线程。
9. FAQ
arena和uordblks差在哪?arena是所有 arena 从系统要来的总量(在用 + 空闲),uordblks只是其中"在用"的部分。差值就是空闲空间,和fordblks大致对应。
为什么mallinfo2()的数比/proc/self/statm小?statm数的是整个进程(代码段、栈、静态区全都算),mallinfo 只数堆的分配。两个数本来就不该相等,交叉验证时应该看uordblks + hblkhd与 RSS 的相对趋势。
程序退出前fordblks不为 0,是泄漏吗?不是。fordblks是空闲块,指针已经交还给 glibc 了,进程退出时一并回收。泄漏的特征是uordblks单调上涨,不是fordblks非零。
man page 说只统计主 arena,源码和实测都说全算,信哪个?信实测,但写防御性代码时别依赖这个细节。本文实测的是 glibc 2.41(demo_arena,4 线程 ×8 MiB 全部计入);BUGS 里那句应是 arena 机制出现前留下的文本。需要权威口径时用malloc_stats()/malloc_info()——前者与mallinfo共用同一套int_mallinfo且逐 arena 打印,后者输出 XML 覆盖更全。
10. 总结
mallinfo()/mallinfo2()是 glibc 留在<malloc.h>里的快速堆体检接口。它的实现没有任何统计子系统——每次调用现场遍历 free list、逐 arena 加总、给你一份副本:平时零成本,调用成本正比于空闲块数。它的出身是 SVID2/XPG 这份上世纪的 Unix 标准,结构体被标准锁死,int字段是 32 位时代的遗产,usmblks恒 0 是接口与实现对不上的活化石;2021 年 2 月 glibc 2.33 才补上mallinfo2()并给老接口挂上 deprecated。
- 新代码:
mallinfo2(),盯uordblks+hblkhd+fordblks的趋势。 - 老代码:出现负数或 deprecation 告警,就是迁移信号——2 GiB 必溢出,第六节有实测。
- 需要全局原子快照、可移植、或换过 allocator:换
malloc_info()或平台自己的观测工具。
参考资料
man 3 mallinfo2(字段定义、BUGS——注意其中"只统计主 arena"一句与 2.41 源码不符)- glibc 2.41 源码:
malloc/malloc.c(int_mallinfo/__libc_mallinfo2/__libc_mallinfo)、malloc/arena.c(arena 环形链表)、NEWS(2.33 变更记录) - 本文 demo 源码(
demo_basic.c、demo_arena.c、demo_cost.c、demo_overflow.c、demo_leak.c)、Makefile与实测输出result.txt一并收在文末资源包里,make result可整套复现
资源地址:https://download.csdn.net/download/weixin_49280144/93509409