调用compare_exchange_weak遇悬空指针会怎样?无锁栈Push为何安全?
一、push中的new_node->next会不会成为悬空指针?
在Anthony Williams的无锁栈初始实现里,不会。核心原因是pop操作的内存管理设计:
- 线程执行pop时,仅将目标节点从栈的原子
head中移除,不会立即释放节点内存,而是暂存到一个待删除列表(比如std::list<Node*>)中。 - 只有确认没有任何线程还在访问该节点时,才会真正释放其内存(通常通过引用计数、或利用内存模型特性判断节点不再被访问)。
再看push的核心流程:
void push(T data) { Node* new_node = new Node(data); new_node->next = head.load(); while (!head.compare_exchange_weak(new_node->next, new_node)) {} }
new_node->next初始指向的是当前head的快照值。即便在load()之后、CAS操作之前,该快照对应的节点被其他线程pop,节点也只是被移到待删除列表,并未被释放——因此new_node->next指向的内存依然有效,不会成为悬空指针。直到CAS操作完成(新节点成功插入栈),原节点的释放也不会影响新节点next指针的有效性:此时栈的结构已由新head维护,其他线程访问栈时只会通过head遍历,不再依赖已被删除节点的next。
二、调用compare_exchange_weak时传入悬空指针会有什么后果?
compare_exchange_weak针对原子指针操作的是指针的地址值,而非指针指向的内存内容,但传入悬空指针依然会引发严重问题,核心是悬空指针本身属于C++标准中的未定义行为范畴,具体场景的后果如下:
作为预期值传入:
若预期值是悬空指针(对应内存已释放),CAS会将原子变量的当前地址值与该悬空地址做比较。如果恰好有新内存分配到这个悬空地址,且原子变量当前值等于该地址,CAS会错误执行替换,直接破坏栈结构,导致数据丢失或线程安全问题。即便没有地址复用,使用悬空指针本身违反内存模型规则,编译器可能生成不可预测的代码(比如优化掉相关操作),导致逻辑彻底混乱。作为新值传入:
若将悬空指针作为新值传入CAS,会让原子指针指向已释放的内存。后续任何访问该原子指针的操作(比如push时的load()、pop时的节点遍历)都会触发对悬空内存的访问,直接导致程序崩溃、数据损坏或其他不可预测行为。
无论哪种情况,传入悬空指针给compare_exchange_weak都会引入未定义行为,是必须严格避免的错误。
内容的提问来源于stack exchange,提问作者teddy

