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

调用C++协程handle的destroy方法触发段错误的原因分析

分析与解决:C++协程task销毁时的段错误问题

问题根源

你的段错误核心原因是协程帧被重复销毁,具体流程拆解如下:

  1. 当协程go()执行完std::cout << "Finished firing\n"后,会进入promise_type::final_suspend()的逻辑。
  2. 你定义的final_suspend返回std::suspend_never,这会告诉C++运行时:协程完成后不需要挂起,直接自动销毁协程帧。
  3. 此时task对象持有的_coro句柄已经变成指向已释放内存的无效句柄。
  4. 当main函数退出,task对象g被销毁,析构函数里调用_coro.destroy(),这相当于对已经被销毁的协程帧再次执行销毁操作,直接触发段错误。

修复方案

要解决这个问题,需要把协程帧的销毁控制权交还给task对象,也就是让协程在完成时挂起,而不是自动销毁:

  1. 修改promise_type的final_suspend,返回std::suspend_always:
std::suspend_always final_suspend() const noexcept { return {}; }
  1. 另外,你的代码里禁用了移动构造和移动赋值运算符,但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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 14:27:44