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

使用co_await时不可拷贝/移动类型出现意外memcpy的问题求助

问题解答

这是编译器bug吗?

是,这属于ARM GCC 11.3的协程实现缺陷。C++标准明确要求:当类型的拷贝/移动构造函数被显式删除时,编译器不能通过memcpy这类字节复制操作来模拟拷贝或移动——哪怕该类型是trivially可移动的。GCC 13.1已修复此问题,证明这是旧版本的实现错误。

对拷贝消除的理解有误吗?

你的核心逻辑是对的:将awaitable设为不可拷贝/不可移动,就是为了强制编译器在目标位置直接构造对象(即触发拷贝消除),保证地址稳定。但GCC 11.3在处理co_await的prvalue操作数时,没有正确遵循这一规则,错误地生成了memcpy来复制不可移动的AwaitableBase成员。

依赖临时对象地址在co_await期间稳定是否属于UB?

不属于。根据C++标准,co_await表达式的awaitable对象生命周期会延续到await_resume()执行完成后。只要你直接构造临时awaitable作为co_await的操作数(而非通过拷贝/移动),其地址在整个等待过程中是稳定有效的——GCC 11.3的bug是例外情况。

针对GCC 11.3的解决方案

以下三种方法可解决该问题:

1. 给Awaitable添加显式构造函数,避免聚合初始化

将Awaitable从聚合类型改为带显式构造函数的类型,让编译器直接在协程帧中构造AwaitableBase成员,避免临时对象拷贝:

struct Awaitable{
    AwaitableBase base;
    bool ready{false};

    // 显式构造函数,直接初始化base
    explicit Awaitable(AwaitableBase) : base() {}
    // 若需传递参数给AwaitableBase,可改为:
    // explicit Awaitable(Params&& params) : base(std::forward<Params>(params)) {}

    bool await_ready() {return false;}
    void await_suspend(std::coroutine_handle<task::promise_type> handle)
    {
        handle.promise().ready_ptr = &ready;
    }
    int await_resume() { return 2; }
};

2. 在协程中声明局部awaitable变量

将临时awaitable改为协程帧内的局部变量,确保地址稳定且无拷贝:

task example()
{
    Awaitable a{make_awaitable_base()};
    co_await a;
}

此方式下a直接分配在协程帧中,co_await直接使用其地址,完全规避临时对象拷贝问题。

3. 调整make_awaitable_base的返回类型

若允许,让工厂函数直接返回Awaitable而非AwaitableBase,减少一层包装的构造开销:

Awaitable make_awaitable()
{
    return Awaitable{};
}

task example()
{
    co_await make_awaitable();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 08:12:05