You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用std::function与std::shared_ptr触发C++标准库双释放问题排查

关于lambda捕获std::shared_ptr导致双释放的问题分析

结论

你的程序触发了未定义行为,libstdc的实现直接暴露了这个问题,而libc的实现细节恰好规避了崩溃,但这不代表程序本身合法。

问题根源拆解

先看代码里的核心错误链:

  1. get()里用std::make_shared<foo>()创建对象,并用shared_ptr<foo> f管理,此时引用计数为1。
  2. 给f->fun赋值了按值捕获f的lambda,循环引用形成:shared_ptr持有foo对象,foo的std::function持有lambda,lambda又持有同一个shared_ptr,引用计数变为2。
  3. 致命错误:get()返回的是*f的裸引用,函数结束后局部变量f销毁,引用计数减为1(只剩lambda里的拷贝持有)。此时返回的引用指向的foo对象,生命周期完全绑定在std::function里的lambda上。
  4. main中执行get().fun = nullptr时,会触发旧std::function的析构:
    • 析构lambda会销毁其持有的shared_ptr<foo>,引用计数归零,进而销毁foo对象。
    • 但此时我们正处于给foo的fun成员赋值的过程中——相当于在销毁foo对象的同时,还在修改它的成员,这属于C++标准明确禁止的对象生命周期重叠操作,直接触发未定义行为。

不同编译器实现的表现差异

  • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 22:45:36