所以你想在你的 C 库中支持自定义分配器

C 库的自定义内存分配器承诺带来更好的性能、灵活性和工具集成,但其设计也引出了关于安全性和实用性的棘手问题。评论者围绕要求调用方传入对象大小、对齐和上下文指针的 API 展开争论——支持者认为这能加快分配并帮助调试,而反对者指出,现实中的 C 代码往往无法可靠跟踪这些细节,反而可能更脆弱。讨论还对比了极简的“单一分配函数”设计与更清晰但更冗长的接口,并强调这些选择如何影响多线程扩展性、与 ASAN/valgrind 等工具的集成,以及对 arena 和 stack allocator 等模式的支持。

分配器 API 的安全性与正确性

  • 几位评论者认为,在 free/realloc 时要求传入大小本可以避免很多 bug,但也有人说信任调用方提供的大小很危险,仍然必须检查。
  • 有人指出,许多分配器已经通过元数据或 arena 知道大小,因此额外的大小参数往往只是重复工作,除非把它们当作提示。
  • 对文章中“调用方总是知道原始大小”的说法,有人提出反驳;他们给出了一些现实中的反例,说明并非如此。

传递大小和对齐信息

  • 支持在 free 中传入大小的人说,这可以避免昂贵的 arena/bucket 查找和元数据访问。
  • 其他人指出,现代分配器通常可以很便宜地恢复大小,而不需要遍历。
  • 多条评论认为,扩展对齐应该是接口的一部分;有人希望每次调用都显式传入对齐参数,也有人依赖上下文或单独的 aligned-alloc API。
  • 讨论还涉及对齐是否是必需的(为了正确 free),还是可以“只是一个提示”。

单函数 vs 多函数分配器接口

  • 有一派喜欢单一的 allocate(ptr, size, ...) 风格 API,通过参数组合来编码 malloc/realloc/free,理由是简单且占用空间小(例如类似 Lua 的设计)。
  • 批评者则认为,把操作合并会让推理更复杂,引入模糊边界情况(如 alloc(NULL, 0)),也让审查和工具支持更困难;他们更偏好明确分开的 alloc/free/realloc。

由调用方管理 vs 由库管理的分配

  • 一些人主张由调用方负责分配的 API,可能通过两遍模式(先计算大小再写入)、可恢复操作,或者像文章中那样“传入一个 allocator 回调”来实现。
  • 另一些人回应说,这只适用于简单场景(例如类似 snprintf 的用法),而复杂库(XML DOM 等)不可避免地会在内部进行分配,并且会受益于可插拔分配器。

线程与性能

  • 讨论指出,全局状态分配器的扩展性很差;线程本地缓存加上跨线程释放机制(队列、CAS、按 size class 的锁)是常见缓解办法。
  • 人们也关注这类方案能否在保持良好多核扩展性的同时,足够简单地实现。

现实中的 C 约束与零大小分配

  • 几条评论强调,许多现有 C 代码库并不会严格跟踪大小,尤其是在使用以 null 结尾的字符串和 flexible arrays 时。
  • 关于 malloc(0) 的含义和用途,以及使用 0 或 -1 之类特殊数值进行带内信号传递,也存在争论。

工具与集成

  • 一些人强调分配器 API 应该与 ASan 和 Valgrind 等工具集成;注解被认为很直接,但至关重要。