C++20协程返回含string字段的临时对象时触发无效释放
C++20协程yield临时Message对象触发无效释放崩溃问题
问题重现
使用C++20协程实现流程逻辑,当协程yield左值Message对象时程序正常运行,但yield临时Message对象时触发malloc: pointer being freed was not allocated错误,崩溃发生在std::string字段销毁阶段。
流程定义(procedure.hpp)
#ifndef PROCEDURE_HPP #define PROCEDURE_HPP #include <coroutine> #include <string> struct Message { std::string value; }; struct Procedure { struct promise_type { Message message; Procedure get_return_object() { return {std::coroutine_handle<promise_type>::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { message = {"Coroutine started"}; return {}; } std::suspend_always final_suspend() noexcept { message = {"Coroutine finished"}; return {}; } std::suspend_always yield_value(Message message) { this->message = message; return {}; } void unhandled_exception() {} void return_void() {} }; std::coroutine_handle<promise_type> handle; }; #endif
崩溃场景代码(main.cpp)
#include <iostream> #include "procedure.hpp" Procedure run() { // 临时对象触发崩溃 co_yield {"message is a temporary"}; } int main() { auto procedure = run(); while (true) { std::cout << "[Message] " << procedure.handle.promise().message.value << std::endl; if (procedure.handle.done()) { return 0; } else { procedure.handle.resume(); } } }
编译运行后输出:
$ g++-12 main.cpp -fcoroutines $ ./a.out [Message] Procedure started [Message] message is a temporary a.out(15267,0x115cd8e00) malloc: *** error for object 0x7fe306c05b50: pointer being freed was not allocated a.out(15267,0x115cd8e00) malloc: *** set a breakpoint in malloc_error_break to debug zsh: abort ./a.out
原因分析
- 协程帧未正确销毁:
std::coroutine_handle不会自动销毁协程帧,main函数中当handle.done()为true时直接返回,未调用handle.destroy()释放协程帧,导致promise对象及其内部的std::string生命周期管理异常。 - GCC 12协程实现的临时对象处理问题:当yield临时对象时,GCC 12的协程实现可能对临时对象的内存管理存在bug,导致
std::string的底层内存被重复释放(临时对象销毁时释放一次,协程帧销毁时再次释放)。
解决方案
方案1:手动销毁协程帧
在main函数中,当协程完成后调用handle.destroy()释放协程帧,确保promise对象正确销毁:
int main() { auto procedure = run(); while (true) { std::cout << "[Message] " << procedure.handle.promise().message.value << std::endl; if (procedure.handle.done()) { procedure.handle.destroy(); // 新增:销毁协程帧 return 0; } else { procedure.handle.resume(); } } }
方案2:修改yield_value为引用传参
将yield_value的参数改为const Message&,避免不必要的拷贝,同时避免临时对象拷贝可能带来的内存管理问题:
std::suspend_always yield_value(const Message& message) { this->message = message; return {}; }
方案3:支持移动语义
添加移动版本的yield_value,利用移动语义减少拷贝,避免临时对象的内存管理冲突:
std::suspend_always yield_value(Message&& message) { this->message = std::move(message); return {}; } // 保留const引用版本兼容左值 std::suspend_always yield_value(const Message& message) { this->message = message; return {}; }
验证
应用上述任意方案后,编译运行临时对象yield的场景,程序可正常输出所有消息且无崩溃:
$ g++-12 main.cpp -fcoroutines $ ./a.out [Message] Procedure started [Message] message is a temporary [Message] Procedure finished
内容的提问来源于stack exchange,提问作者Noah
相关产品推荐
相关产品推荐

