C++20链式协程任务示例触发段错误问题咨询
协程代码本地段错误排查问题
在《C++20 完全指南》一书中,Josuttis用以下代码演示了如何让协程实现awaiter接口,从而存储子协程,并在外部协程恢复时自动恢复子协程。我理解了代码逻辑,但本地执行编译命令:g++ -std=c++20 corocorosub.cpp -O3 -o corocorosub && ./corocorosub
运行时触发段错误,输出日志如下:
MAIN: callCoro() initialized
callCoro(): CALL coro()
MAIN: callCoro() suspended
coro(): PART1
MAIN: callCoro() suspended
coro(): PART2
MAIN: callCoro() suspended
callCoro(): coro() done
MAIN: callCoro() suspended
Segmentation fault (core dumped)
但在编译器在线运行环境中该代码完全正常,无法确定问题出在代码还是编译器,特此咨询问题原因及排查方向。
相关代码如下:
#include <coroutine> #include <exception> #include <iostream> class [[nodiscard]] CoroTaskSub { public: struct promise_type; using CoroHdl = std::coroutine_handle<promise_type>; private: CoroHdl hdl; public: struct promise_type { CoroHdl subHdl = nullptr; auto get_return_object() { return CoroTaskSub{CoroHdl::from_promise(*this)}; } auto initial_suspend() { return std::suspend_always{}; } void unhandled_exception() { std::terminate(); } void return_void() { } auto final_suspend() noexcept { return std::suspend_always{}; } }; CoroTaskSub(auto h) : hdl{h} { } ~CoroTaskSub() { if (hdl) { hdl.destroy(); } } CoroTaskSub(const CoroTaskSub&) = delete; CoroTaskSub& operator=(const CoroTaskSub&) = delete; bool resume() const { if (!hdl || hdl.done()) { return false; } CoroHdl innerHdl = hdl; while (innerHdl.promise().subHdl && !innerHdl.promise().subHdl.done()) { innerHdl = innerHdl.promise().subHdl; } innerHdl.resume(); return !hdl.done(); } bool await_ready() { return false; } void await_suspend(auto awaitHdl) { awaitHdl.promise().subHdl = hdl; } void await_resume() {} }; CoroTaskSub coro() { std::cout << " coro(): PART1\n"; co_await std::suspend_always{}; std::cout << " coro(): PART2\n"; } CoroTaskSub callCoro() { std::cout << " callCoro(): CALL coro()\n"; co_await coro(); std::cout << " callCoro(): coro() done\n"; co_await std::suspend_always{}; std::cout << " callCoro(): END\n"; } int main() { auto coroTask = callCoro(); std::cout << "MAIN: callCoro() initialized\n"; while (coroTask.resume()) { std::cout << "MAIN: callCoro() suspended\n"; } std::cout << "MAIN: callCoro() done\n"; }
问题原因分析
- 协程句柄重复销毁:
CoroTaskSub的析构函数会销毁持有的协程句柄,但callCoro()中co_await coro()时,coro()返回的临时CoroTaskSub对象在await_suspend完成后会被销毁,此时会销毁子协程的句柄,但父协程的subHdl还持有这个已经被销毁的句柄,后续resume()中访问该句柄会触发野指针访问,导致段错误。 - 编译器版本差异:不同版本GCC对协程临时对象的生命周期处理存在差异,在线环境的GCC版本可能延长了临时对象生命周期或优化行为不同,避免了问题,但本地版本未做处理。
排查与修复方向
- 检查本地GCC版本:运行
g++ --version查看版本,C++20协程在GCC 10+才初步支持,版本过低可能存在协程相关Bug,建议升级到GCC 12及以上版本。 - 修复生命周期问题:修改
await_suspend函数,转移协程句柄所有权,避免临时对象销毁时销毁子协程句柄:void await_suspend(auto awaitHdl) { awaitHdl.promise().subHdl = hdl; hdl = nullptr; // 转移所有权,临时对象析构时不再销毁句柄 } - 禁用优化测试:去掉
-O3选项编译运行,看是否还出现段错误——优化可能导致临时对象被提前销毁,暴露生命周期问题。 - 用调试工具定位:使用GDB调试,运行
gdb ./corocorosub,触发段错误后用backtrace查看调用栈,确认段错误发生位置。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

