C++20 协程的 frame 默认走堆分配——每次进入协程函数,编译器插入一次operator new。编译器可以做 Heap Allocation eLision Optimization(HALO),把 frame 搬到 caller 的栈上,但它有三个严格的前提:协程 lifetime 必须被 caller 完全包含、handle 不能逃逸到其他线程、不能被存入容器。本文逐一拆解 HALO 失效的五类场景,给出-fdump-tree-coroutine的编译期诊断方法,并提供自定义promise_type::operator new的 arena 分配器兜底方案。一个co_await背后的malloc调用在一个高频调用的异步管线里跑 perf,你大概率会看到一个让人不安的现象:operator new的调用次数和协程的创建次数几乎是 1:1 的。不是co_await本身在 malloc。是每次进入一个协程函数时,编译器在你看不到的地方插入了一次堆分配。这次分配申请的内存,承载的是整个协程的运行时状态——C++ 标准里叫它 coroutine frame。看一段最简单的协程,GCC 14 /-O2/-std=c++20: