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

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版本可能延长了临时对象生命周期或优化行为不同,避免了问题,但本地版本未做处理。

排查与修复方向

  1. 检查本地GCC版本:运行g++ --version查看版本,C++20协程在GCC 10+才初步支持,版本过低可能存在协程相关Bug,建议升级到GCC 12及以上版本。
  2. 修复生命周期问题:修改await_suspend函数,转移协程句柄所有权,避免临时对象销毁时销毁子协程句柄:
    void await_suspend(auto awaitHdl) {
      awaitHdl.promise().subHdl = hdl;
      hdl = nullptr; // 转移所有权,临时对象析构时不再销毁句柄
    }
    
  3. 禁用优化测试:去掉-O3选项编译运行,看是否还出现段错误——优化可能导致临时对象被提前销毁,暴露生命周期问题。
  4. 用调试工具定位:使用GDB调试,运行gdb ./corocorosub,触发段错误后用backtrace查看调用栈,确认段错误发生位置。

内容的提问来源于stack exchange,提问作者Enlico

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 12:15:29