使用std::function与std::shared_ptr触发C++标准库双释放问题排查
结论
你的程序触发了未定义行为,libstdc的实现直接暴露了这个问题,而libc的实现细节恰好规避了崩溃,但这不代表程序本身合法。
问题根源拆解
先看代码里的核心错误链:
get()里用std::make_shared<foo>()创建对象,并用shared_ptr<foo> f管理,此时引用计数为1。- 给
f->fun赋值了按值捕获f的lambda,循环引用形成:shared_ptr持有foo对象,foo的std::function持有lambda,lambda又持有同一个shared_ptr,引用计数变为2。 - 致命错误:
get()返回的是*f的裸引用,函数结束后局部变量f销毁,引用计数减为1(只剩lambda里的拷贝持有)。此时返回的引用指向的foo对象,生命周期完全绑定在std::function里的lambda上。 main中执行get().fun = nullptr时,会触发旧std::function的析构:- 析构lambda会销毁其持有的
shared_ptr<foo>,引用计数归零,进而销毁foo对象。 - 但此时我们正处于给
foo的fun成员赋值的过程中——相当于在销毁foo对象的同时,还在修改它的成员,这属于C++标准明确禁止的对象生命周期重叠操作,直接触发未定义行为。
- 析构lambda会销毁其持有的
不同编译器实现的表现差异
- libstdc++:
std::function赋值时会先析构旧对象(lambda),直接触发foo销毁,而赋值操作还未完成,后续内存操作访问已释放的foo,导致双释放报错。 - libc++:其
std::function的赋值逻辑可能先完成新值构造再析构旧值,刚好避开了这个场景的崩溃,但这只是实现细节带来的侥幸,程序的未定义行为本质没变。
修复方案
必须避免返回生命周期由shared_ptr管理的对象的裸引用,正确做法是让get()返回shared_ptr<foo>,确保对象生命周期被正确管控:
#include <memory> #include <functional> struct foo { std::function<void(void)> fun; }; std::shared_ptr<foo> get() { auto f = std::make_shared<foo>(); f->fun = [f]() {}; return f; } int main(void) { auto foo_ptr = get(); foo_ptr->fun = nullptr; return 0; }
内容的提问来源于stack exchange,提问作者Patrick Ziegler
相关产品推荐
相关产品推荐

