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

Visual Studio 2022中C++20协程局部变量致栈溢出问题咨询

C++20协程栈溢出问题分析

在Visual Studio 2022(C++20编译环境)中,运行以下代码会触发栈溢出异常(operator new从未被调用):

struct cCoro
{
    struct promise_type
    {
        cCoro get_return_object() {
            return {
                // 使用C++20指定初始化语法
                .Handle = std::coroutine_handle<promise_type>::from_promise(*this)
            };
        }

        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() {}
        void return_void() {}

        void* operator new (size_t sz) {
            printf("%s %I64d\n", __FUNCTION__, sz);
            return malloc(sz);
        }
    };

    std::coroutine_handle<promise_type> Handle;
};

cCoro stack_or_heap()
{
    const int sz = 64 * 1024 * 1024; // 远大于普通线程栈大小,触发栈溢出
    uint8_t buffer[sz];

    co_await std::suspend_always();

    // ... 此处使用buffer
}

int main()
{
    auto h = stack_or_heap(); // <== 此处触发栈溢出
}

将buffer大小修改为以下值时,代码可正常运行(且operator new会被调用):

cCoro stack_or_heap()
{
    const int sz = 64 * 1024; // 运行正常
    uint8_t buffer[sz];

    co_await std::suspend_always();

    // ... 此处使用buffer
}

针对上述现象,解答以下三个疑惑:

1. C++协程中的“局部”变量是存储在栈上还是堆上?

C++协程中的局部变量(包括示例中的buffer)最终会存储在协程状态对象中,而协程状态对象默认通过堆分配(operator new)创建。但在协程初始化阶段,编译器可能先在当前调用栈上临时分配空间,用于构造协程状态,之后再将其转移到堆上(如果需要持久化挂起状态)。

2. 无论buffer大小如何,我预期其分配都会通过operator new完成,为何会出现栈溢出?

这是Visual Studio编译器的协程栈分配优化策略导致的:当协程局部变量总大小较小时,编译器会先尝试在当前调用栈上完整分配协程状态所需空间,再决定是否移到堆上;当变量总大小超过某个阈值(你的环境中约为64KB),编译器会直接使用堆分配。

第一个例子中,buffer大小为64MB,远超线程栈默认大小(通常1MB或8MB),编译器在栈上尝试预留空间时直接触发溢出,根本没走到堆分配逻辑,所以operator new从未被调用。

3. 查看Godbolt反汇编代码,进入协程时会在栈上预留buffer大小的空间,同时又通过operator new为协程状态(包含buffer)分配空间,为何会为同一buffer同时在栈和堆上分配空间?

这是编译器的临时栈分配+拷贝迁移机制:

  • 编译器先在调用栈上为协程状态(包括局部变量)分配临时空间,用于初始化promise对象、局部变量等;
  • 由于你的代码中initial_suspend返回suspend_always,协程会立即挂起,此时编译器会将栈上的协程状态完整拷贝到operator new分配的堆空间中;
  • 后续协程的挂起、恢复操作都基于堆上的状态执行,栈上的临时空间会在协程初始化函数返回时被释放。

当局部变量过大时,栈上的临时分配直接触发溢出,导致后续堆分配逻辑无法执行。


内容的提问来源于stack exchange,提问作者Dr James Cain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 21:01:36