You need to enable JavaScript to run this app. C++20协程存取move-only数据时await_resume返回左值致段错误的解决方法
优惠活动
大模型
产品
解决方案
定价
更多

C++20协程存取move-only数据时await_resume返回左值致段错误的解决方法

段错误根因
  • 核心触发点:await_ready()固定返回true,完全跳过await_suspend()的执行。根据C++协程规范,当await_ready()返回true时,协程不会挂起,也不会传递当前协程句柄给awaiter,导致awaitalbe实例的handle成员是默认构造的空句柄(等价于nullptr)。后续在await_resume()中访问handle.promise()就是空指针解引用,直接触发段错误。
  • 执行顺序错配:promise_type的initial_suspend()返回suspend_never,意味着协程创建后会立刻从头执行。在main中调用f()创建协程时,协程会一路跑到第一个co_await表达式位置,此时main还没执行set_data()给promise填充数据,就算句柄初始化正常,move出来的unique_ptr也是空的,后续解引用同样会触发段错误。
修正方案

按照问题点逐一调整:

  1. 修正awaitable逻辑,消除空句柄问题
    强制走await_suspend流程拿到合法协程句柄,同时避免不必要的实际挂起:
    • 将await_ready()的返回值改为false,确保一定会调用await_suspend,完成句柄的赋值
    • 将await_suspend()的返回类型从void改为bool,存入句柄后返回false,通知协程不要真正挂起,直接恢复执行
    • 将await_resume()的返回类型从右值引用改为值类型,直接move出仅移动数据,避免悬空引用风险
      调整后的awaitable代码:
    struct awaitalbe
    {
        bool await_ready() { return false; }
        bool await_suspend(std::coroutine_handle<task::promise_type> handle) 
        { 
            this->handle = handle;
            return false; // 不实际挂起协程
        }
        task::promise_type::data_type await_resume()
        {
            return std::move(handle.promise().data);
        }
    
        std::coroutine_handle<task::promise_type> handle;
    };
    
  2. 调整initial_suspend,保证数据注入窗口
    将promise_type中的initial_suspend()从suspend_never改为suspend_always,让协程创建完成后直接挂在起点,不会提前向下执行,给外部留足空间调用set_data()注入数据:
    std::suspend_always initial_suspend() const noexcept { return {}; }
    
  3. 补充task的析构逻辑
    由于final_suspend()返回suspend_always,协程执行完成后会挂在终点,不会自动释放协程帧内存。需要在task的析构函数中判断句柄有效性,主动销毁协程,避免内存泄漏:
    ~task()
    {
        if (handle)
        {
            handle.destroy();
        }
    }
    
调整后的执行流程
  • main中调用f()创建协程,构造promise并生成task对象
  • 协程因initial_suspend的设置直接挂起在起点,不向下执行
  • main拿到task实例,通过set_data()将构造好的仅移动数据move进协程的promise中
  • 调用task1.resume(),协程从起点恢复执行,走到co_await awaitalbe{}位置
  • 触发await_ready返回false,进入await_suspend流程完成句柄赋值,返回false不实际挂起
  • 直接调用await_resume将promise中的数据move出来,赋值给协程内的p2
  • 协程继续执行,正常输出数据值,最终挂在final_suspend位置
  • task对象离开作用域时,自动调用handle.destroy()释放协程帧内存

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.25 19:24:04