c++无内置垃圾回收器,实现追踪式gc需手动管理指针元信息:raw pointer无法区分合法引用与垃圾,须通过显式注册根集、栈/寄存器扫描、写屏障等机制确保准确标记。

为什么不能直接用 raw pointer 做标记扫描
原始指针 int*、MyObj* 在内存中就是纯地址值,GC 无法区分它是指向堆对象的合法引用,还是临时计算出的整数、未初始化的栈垃圾、或只是对齐填充字节。保守 GC 会把所有看起来像指针的位模式都当引用处理(易漏标/误标),精确 GC 则要求你**明确知道哪些栈/寄存器/全局变量里存着有效指针**。
- 栈上局部变量若未显式注册,
&local_ptr地址可能被 GC 忽略,导致存活对象被误回收 - 编译器优化(如寄存器分配、
register关键字、尾调用)会让指针“消失”于栈帧,GC 扫不到 -
reinterpret_cast<uintptr_t>(ptr)</uintptr_t>后再转回,会破坏 GC 对指针的识别逻辑
如何让 GC 准确识别并标记活跃指针
核心思路:**不依赖运行时猜测,而是由程序员/编译器辅助提供指针元信息**。
- 使用
std::shared_ptr或自定义句柄类包装堆对象,GC 可通过重载operator&或访问内部_M_ptr字段(需 ABI 稳定)获取真实地址——但注意:这仅适用于你完全控制对象生命周期的场景 - 在进入 GC 标记前,用平台相关 API 冻结线程并遍历其栈:
GetThreadContext(Windows)、ucontext_t+getcontext(Linux),逐字节检查是否落在已知堆区间内;配合编译器插桩(如-fsanitize=address的 shadow memory)可提升精度 - 强制要求所有根集(roots)存于显式注册的结构体中,例如:
struct GCRoots { std::vector<void> stack_roots; std::vector<void> global_roots; };</void></void>每次分配后调用gc_register_root(&ptr),标记阶段只扫这个容器
标记阶段性能关键:别碰指针值本身,只查元数据
真正拖慢标记的是随机内存访问和缓存失效,不是指针解引用本身。优化方向是减少访存、利用局部性。
- 把堆划分为固定大小页(如 4KB),每页配一个
bitmap表示哪些槽位存有效对象;标记时按页扫描 bitmap,跳过全 0 页——std::vector<uint8_t></uint8_t>存 bitmap 比遍历指针数组快得多 - 避免在标记循环里调用虚函数或动态 dispatch;把对象类型 tag 提前存入 header,用查表(
static constexpr std::array<size_t num_types></size_t>)代替dynamic_cast - 多线程标记时,每个 worker 线程独占一块待扫描栈区域,用
std::atomic<size_t></size_t>分配下一个 chunk 下标,避免锁竞争
容易被忽略的陷阱:指针别名与写屏障缺失
标记阶段假设“从根出发能到达的所有对象都是活的”,但如果在标记中途发生赋值:obj->next = new_obj,而 new_obj 尚未被扫描到,就可能漏标。
- 必须插入写屏障(write barrier):在
obj->next = new_obj之前,调用gc_write_barrier(&obj->next, new_obj),将new_obj加入灰色集合或直接标记 - 禁止用
memcpy、std::copy批量复制含指针字段的结构体——它们绕过写屏障;应改用自定义copy_with_barrier() - 若使用
std::vector<:unique_ptr>></:unique_ptr>,其内部指针数组本身也要被 GC 扫描;否则 vector realloc 后旧内存块可能被提前回收
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











