C++20协程自定义enumerable生成器出现use after free崩溃问题求助
问题根源
核心是协程悬挂+临时对象生命周期不匹配导致的悬垂指针访问,属于C++标准定义的未定义行为:
你的where是协程函数,且promise_type::initial_suspend返回std::suspend_always,所以where被调用时会立刻挂起,将构造好的enumerable对象返回给调用方,协程内部的遍历、过滤逻辑要等到第一次调用begin()恢复协程时才会执行。
崩溃场景的执行流程
filtered_numbers函数内,numbers()返回一个临时enumerable<int>对象,调用它的where方法。where协程立刻挂起,返回新的enumerable<int>对象作为filtered_numbers的返回值。filtered_numbers函数执行结束,函数作用域内的所有临时对象(包括numbers()返回的上游enumerable)被析构。- main函数中遍历
filtered_numbers返回的对象,调用begin()恢复where协程,此时协程内访问的this指针(指向已经析构的上游enumerable对象)是悬垂指针,访问非法内存直接崩溃。
第二个main看似正常的原因
这只是未定义行为的“巧合”:你直接在range-for里写numbers().where(...),上游enumerable临时对象析构后,它占有的栈内存还没有被其他数据覆盖,所以where协程读取this->handle时还能拿到有效的地址值,刚好能正常执行。一旦程序复杂度提升,这块内存被其他逻辑改写,一样会崩溃。
可行解决方案
核心思路是将上游生成器的所有权转移到协程帧中,保证上游生成器的生命周期和where协程的生命周期一致,避免悬垂:
- 首先将
enumerable改为move-only类型,补充移动构造、移动赋值和正确的资源释放逻辑:
enumerable(const enumerable&) = delete; enumerable& operator=(const enumerable&) = delete; enumerable(enumerable&& other) noexcept : handle(std::exchange(other.handle, nullptr)) {} enumerable& operator=(enumerable&& other) noexcept { if (this != &other) { if (handle) handle.destroy(); handle = std::exchange(other.handle, nullptr); } return *this; } ~enumerable() { if (handle) { std::cout << "enumerable destructed: " << this << " : " << handle.address() << '\n'; handle.destroy(); } }
- 修改
where方法,仅允许右值调用,且将上游生成器移动到协程帧的局部变量中持有:
template<class Predicate> enumerable<ReturnType> where(Predicate&& pred) && { // 将上游生成器移动到协程帧局部变量,生命周期和协程绑定 auto upstream = std::move(*this); std::cout << "where: " << &upstream << " : " << upstream.handle.address() << '\n'; for (auto& i : upstream) { if(pred(i)) co_yield i; } } // 可选:禁止左值调用where避免误用 template<class Predicate> enumerable<ReturnType> where(Predicate&& pred) & = delete;
修改后不管是哪种调用场景,上游生成器都会被移动到where协程帧里持有,不会被提前析构,也不会出现悬垂指针问题,同时保留了initial_suspend返回std::suspend_always的惰性执行行为。
内容的提问来源于stack exchange,提问作者ahhhaghost
相关产品推荐
相关产品推荐

