Skip to content
← 返回C列表
知识专题

C 定长池:布局、归还与故障边界 ​

容量、对齐与空闲链 ​

先会读 C 指针、size_t 和 malloc/free;本节用定长池解释块宽、步长及容量。对照完整项目的 slab.h 与 slab.c阅读,示例未执行。

专题入口:C 定长分配器:容量、对齐与空闲链 · C 定长分配器:归还、复用与销毁契约 · C 定长分配器完整实践:为 PMR 提供存储 · C 定长分配器排错:溢出、悬空与容量

C定长池示例采用什么标准 ​

C定长池示例采用什么标准:采用 C11,C++ 适配端采用 C++20。项目限定单线程,先掌握指针、malloc/free、size_t 与分离编译;这里的 slab 是一段 backing 分成固定块的教学实现,没有页级回收、多尺寸类或线程缓存。Windows、Linux、macOS 需各自已有工具链;本轮不安装、不执行。

C定长池为什么要区分块宽和步长 ​

C定长池为什么要区分块宽和步长:width 是调用者可用字节上限,stride 是块起点的间隔。malloc 保证基本对齐,stride 必须是 _Alignof(max_align_t) 的整数倍,第二块才继续满足对齐。17字节对象在16字节基本对齐环境下需要32字节步长;内部浪费15字节是该实例结果,并非所有平台固定如此。先查 width <= SIZE_MAX-(a-1),再算 ((width+a-1)/a)*a;最后查 stride <= SIZE_MAX/count,才允许分配总长。

C空闲链和位图分别保护什么 ​

C空闲链和位图分别保护什么:free list 通过 next[index] 串起未借出槽,head==count 表示空。位图的第i位表示槽i当前借出,live记录借出总数;0<=i<count<=64 保证移位合法。取块弹出链头并置位;归还先验证起点及位,再清位和压回链头。元数据与负载分离,用户写满 width 字节不会覆盖 next;用户越界仍可能破坏相邻槽,池本身不是隔离机制。

C定长池的复杂度和内存上界是多少 ​

C定长池的复杂度和内存上界是多少:取块为 O(1),本实现归属检查逐槽等值扫描为 O(count),count至多64;不要把整个分配器称为严格常数时间实时保证。C池初始化后不再向系统分配,负载上界是 stride*count,另有句柄和元数据;malloc内部成本与碎片仍存在。C++适配器默认允许上游,因此整体内存不再有此上界。

独立练习 ​

设width=17、count=3、a=16,求stride、负载字节与浪费。为什么不能把head==count的槽取出?

参考答案(先独立推演)

stride=32,backing=96,三个完整请求共51字节,槽内浪费45字节;head==count是链尾哨兵,取它会访问越界next并使移位不合法。

一手资料与边界 ​

2026-10-17核对 WG14 C11 N1570 6.2.8、6.5.6、6.5.8与7.22.3;C++资源协议工作草案用于溯源,滚动草案的新接口不自动纳入C++20;Allocator要求用于核对传播边界。国内资料核读 Microsoft中文memory_resource概览,平台实现说明不推广为所有标准库。项目未编译、未执行,普通静态模板通过不能证明内存安全。

归还、复用与销毁契约 ​

先理解前节空闲链和位图;本节检查归还指针、复用及销毁。对应接口和实现见slab.h 与 slab.c,示例未执行。

专题入口:C 定长分配器:容量、对齐与空闲链 · C 定长分配器:归还、复用与销毁契约 · C 定长分配器完整实践:为 PMR 提供存储 · C 定长分配器排错:溢出、悬空与容量

C怎样判断归还指针是否属于池 ​

C怎样判断归还指针是否属于池:本实现把待检查值与每个块起点做等值比较,不对不相关对象指针做减法或大小比较。只接受精确块起点,内部偏移和外部仍存活对象的地址会被拒绝。这是有限候选匹配,不是通用指针有效性验证;调用者必须传入可合法求值的确定指针值,伪造值、失效值和并发销毁不在可诊断契约内。

C位图能否发现所有重复释放 ​

C位图能否发现所有重复释放:位图能发现未发生复用时的再次归还;复用之后旧别名与新借出槽有相同地址,位图无法区分它们。旧拥有者继续归还将偷走新拥有者的块。需要检测这种错误应改用索引加代数的句柄并检查代际,代数回绕也须设计;本项目不把裸指针接口包装成这种保证。

C池耗尽或初始化失败后状态怎样 ​

C池耗尽或初始化失败后状态怎样:池耗尽和不支持的字节数或对齐请求返回 NULL,head、位图、live保持不变。初始化先验证加法和乘法,再分配句柄及backing;第二次malloc失败就释放句柄,不发布半初始化状态。零字节请求在C接口被拒绝,C++端则允许按PMR契约转上游;两层策略不同必须明确。

C池什么时候可以安全销毁 ​

C池什么时候可以安全销毁:live非零时 slab_destroy 返回-1并保留完整池,调用者必须先结束对象使用、归还所有槽再销毁。句柄是唯一拥有者,不能浅复制后各自释放;NULL销毁可以成功,销毁成功后的旧句柄不可再传入。没有通用reset函数,因为强制重置会让全部借用指针瞬间失效,容易掩盖生命周期错误。

独立练习 ​

写出A归还、B复用、A旧别名再次归还的状态序列,判断位图能否发现。

参考答案(先独立推演)

位图经历1→0→1;A再次归还看到1会成功并破坏B拥有权。裸指针无法区分代际,不能把这项缺陷宣传成完全防重复释放。

一手资料与边界 ​

2026-10-17核对 WG14 C11 N1570 6.2.8、6.5.6、6.5.8与7.22.3;C++资源协议工作草案用于溯源,滚动草案的新接口不自动纳入C++20;Allocator要求用于核对传播边界。国内资料核读 Microsoft中文memory_resource概览,平台实现说明不推广为所有标准库。项目未编译、未执行,普通静态模板通过不能证明内存安全。

溢出、悬空与容量 ​

按“对齐 → 请求大小 → 槽位归属 → 线程归属”定位失败,对照完整项目及失败测试推演;本节没有实际故障运行记录。

专题入口:C 定长分配器:容量、对齐与空闲链 · C 定长分配器:归还、复用与销毁契约 · C 定长分配器完整实践:为 PMR 提供存储 · C 定长分配器排错:溢出、悬空与容量

C池第二块为什么可能未对齐 ​

C池第二块为什么可能未对齐:先检查 stride 是否按max_align_t取整,而不是只检查backing起点。还要检查请求alignment为非零二次幂、并整除池支持的基本对齐;扩展对齐请求必须拒绝或交给支持它的上游。将指针强转成更高对齐类型不会修复底层地址。

C池为什么空闲很多却分配失败 ​

C池为什么空闲很多却分配失败:看请求width上限和alignment支持范围,再看free list是否已空。固定块池不会合并多个空槽满足更大连续请求;即使总剩余字节够,单个请求超过块宽仍失败。不要调整容量掩盖大小类选择错误;先统计请求尺寸分布、峰值存活和失败次数,再选择分层池或系统分配。

C池释放后读数据没崩溃是否安全 ​

C池释放后读数据没崩溃是否安全:池归还槽后backing仍存活,ASan未必把该槽视为系统free,读到原字节不代表对象生存期或拥有权仍合法。用毒化字节也只提高发现概率;归还后必须停止通过旧借用访问,复用会覆盖旧数据。C++元素需要先析构再归还存储,二者是不同动作。

C池加入原子head就能多线程使用吗 ​

C池加入原子head就能多线程使用吗:不能。head、next、位图和live组成共同不变式,单独把head原子化会留下数据竞争并引入ABA和回收问题。当前契约严格单线程;若需共享,先用同一互斥锁覆盖所有取块、归还、状态读取和销毁,并规定销毁时外部没有访问者。线程本地池还需解决跨线程归还。

独立练习 ​

两个线程共享池,只给take加锁是否足够?列出遗漏。

参考答案(先独立推演)

不够:give改head/next/位图/live,live读取及destroy也需同步;销毁还要保证无外部访问者,锁对象自身不能先销毁。

一手资料与边界 ​

2026-10-17核对 WG14 C11 N1570 6.2.8、6.5.6、6.5.8与7.22.3;C++资源协议工作草案用于溯源,滚动草案的新接口不自动纳入C++20;Allocator要求用于核对传播边界。国内资料核读 Microsoft中文memory_resource概览,平台实现说明不推广为所有标准库。项目未编译、未执行,普通静态模板通过不能证明内存安全。

继续阅读