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

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

原因分析

  1. 协程帧未正确销毁:std::coroutine_handle不会自动销毁协程帧,main函数中当handle.done()为true时直接返回,未调用handle.destroy()释放协程帧,导致promise对象及其内部的std::string生命周期管理异常。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 16:05:39