调用C++协程handle的destroy方法触发段错误的原因分析
分析与解决:C++协程task销毁时的段错误问题
问题根源
你的段错误核心原因是协程帧被重复销毁,具体流程拆解如下:
- 当协程
go()执行完std::cout << "Finished firing\n"后,会进入promise_type::final_suspend()的逻辑。 - 你定义的
final_suspend返回std::suspend_never,这会告诉C++运行时:协程完成后不需要挂起,直接自动销毁协程帧。 - 此时
task对象持有的_coro句柄已经变成指向已释放内存的无效句柄。 - 当
main函数退出,task对象g被销毁,析构函数里调用_coro.destroy(),这相当于对已经被销毁的协程帧再次执行销毁操作,直接触发段错误。
修复方案
要解决这个问题,需要把协程帧的销毁控制权交还给task对象,也就是让协程在完成时挂起,而不是自动销毁:
- 修改
promise_type的final_suspend,返回std::suspend_always:
std::suspend_always final_suspend() const noexcept { return {}; }
- 另外,你的代码里禁用了移动构造和移动赋值运算符,但
go()返回task时需要移动语义(协程返回对象必须可移动),所以需要实现这两个函数,避免编译或运行时的其他问题:
task(task&& rhs) noexcept : _coro{rhs._coro} { rhs._coro = nullptr; } task& operator=(task&& rhs) noexcept { if (this != &rhs) { if (_coro) _coro.destroy(); _coro = rhs._coro; rhs._coro = nullptr; } return *this; }
完整修复后的代码
#include <iostream> #include <coroutine> class task { public: struct promise_type; using handle = std::coroutine_handle<promise_type>; struct promise_type { std::suspend_never initial_suspend() const noexcept { return {}; } // 修改为suspend_always,让协程完成后挂起 std::suspend_always final_suspend() const noexcept { return {}; } auto get_return_object() noexcept { return task{handle::from_promise(*this)}; } void unhandled_exception() { std::terminate(); } }; ~task() { if (_coro != nullptr) { std::cout << "Destroying task\n"; _coro.destroy(); } } task(task const&) = delete; task& operator=(task const&) = delete; // 实现移动构造和移动赋值 task(task&& rhs) noexcept : _coro{rhs._coro} { rhs._coro = nullptr; } task& operator=(task&& rhs) noexcept { if (this != &rhs) { if (_coro) _coro.destroy(); _coro = rhs._coro; rhs._coro = nullptr; } return *this; } private: handle _coro = nullptr; task(handle coro) : _coro{coro} { std::cout << "Created task\n"; } }; std::coroutine_handle<> resume_handle; struct pause : std::suspend_always { void await_suspend(std::coroutine_handle<> h) { resume_handle = h; } }; task go() { co_await pause(); std::cout << "Finished firing\n"; } int main(int argc, char *argv[]) { auto g = go(); resume_handle(); return 0; }
补充说明
C++协程的final_suspend是控制协程完成后行为的关键:
- 返回
std::suspend_never:协程完成后立即销毁帧,句柄失效。 - 返回
std::suspend_always:协程完成后保持挂起状态,直到调用句柄的destroy()才销毁帧。
对于task这种需要手动管理生命周期的协程包装器,必须使用std::suspend_always作为final_suspend的返回值,确保只有task对象才能销毁协程帧,避免重复销毁或野指针问题。
内容的提问来源于stack exchange,提问作者James Peach
相关产品推荐
相关产品推荐

