位图去重必须用裸指针(如uint8_t*)而非数组名,因为数组名是右值、不可取地址,std::vector是代理类不支持直接位操作,只有可解引用、可偏移、内存对齐的裸指针才能安全高效执行test_and_set_bit等底层位运算。

为什么位图去重必须用指针而非数组名
因为位图本质是按比特(bit)操作连续内存,std::vector<bool></bool> 是代理类、不支持取地址,bool[] 数组名是右值、无法传递给需要修改原始内存的位操作函数。只有 uint8_t* 或 uint64_t* 这类可解引用、可偏移、可原子读写的裸指针,才能直接映射到内存块并做 test_and_set_bit 类操作。
常见错误:用 auto bitmap = new bool[SIZE] —— 每个 bool 占 1 字节,浪费 7/8 空间,且无法用位运算批量处理;或者传入 &bitmap[0] 但类型是 bool*,后续做 ptr[i] |= (1 会触发未定义行为(<code>bool* 解引用后赋值非 0/1 值)。
- 务必用
uint8_t*或uint64_t*分配原始内存,例如:uint8_t* bits = new uint8_t[(max_val + 7) / 8]{0} - 若需线程安全,优先选
std::atomic<uint64_t>*</uint64_t>(对齐到 8 字节),避免用锁包装整个位图 - 释放时必须用
delete[] bits,不能漏掉[],否则只析构首元素
如何用指针快速定位并设置单个 bit
核心是把整数 val 映射到字节索引和位偏移:字节下标 = val / 8,位偏移 = val % 8。用指针算术直接跳转,比封装成函数调用快一个数量级(尤其在 tight loop 中)。
示例逻辑(无锁,假设 val ):
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
uint8_t* p = bits + (val >> 3); // 等价于 val / 8,位移更快 uint8_t mask = 1U
- 用
>> 3和& 7替代除法和取模,编译器虽常优化,但显式写出更可靠 -
mask必须是uint8_t类型,避免1 在 int 上溢出(某些平台 <code>int是 16 位) - 不要写
bits[val / 8] |= ...—— 每次都重新计算地址,指针变量复用更高效
64 位指针对齐与批量扫描的实操要点
当去重范围大(如 0–10⁷),用 uint64_t* 指针配合 _mm_popcnt64 或 __builtin_popcountll 批量统计已设位数,比逐字节查快 4–8 倍。但前提是内存起始地址 8 字节对齐,否则 uint64_t* 解引用可能 SIGBUS。
- 分配时用
aligned_alloc(8, size)或std::aligned_alloc(C++17),再强转为uint64_t* - 扫描前先处理低地址残留字节(
(uintptr_t)ptr % 8),再进入 64 位对齐主循环 - 注意
__builtin_popcountll在 GCC/Clang 下可用,MSVC 需用__popcnt64,且要加/arch:AVX2编译选项 - 若用
std::atomic<uint64_t>*</uint64_t>,确保对齐后才能用fetch_or原子操作,否则行为未定义
边界检查与内存泄漏的隐性陷阱
位图去重最常崩在越界:val 超过预分配范围却没校验,导致写到相邻变量或堆元数据上。而 new 分配的裸指针不会自动带边界信息,调试器也难捕获。
- 构造时存下容量(
size_t capacity_bytes),每次操作前检查val >= max_val,而不是只靠val / 8 - 用 RAII 封装指针(如自定义
BitMap类),析构中置空指针并delete[],避免悬挂指针 - 测试时开启 AddressSanitizer:编译加
-fsanitize=address,能立刻捕获越界写和 use-after-free - 别依赖
memset(bits, 0, size)初始化 —— 若 size 为 0,某些 libc 实现会崩溃;改用if (size) memset(...)
真正麻烦的不是怎么设 bit,而是谁负责对齐、谁保证不越界、谁在多线程里确保原子性 —— 这些都得靠指针本身之外的契约来兜底。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










