I'm using C++20 coroutines to implement procedures for my application. Procedures run until they have a message to report to the application, at which point they suspend until the application resumes them. Each message contains some text stored in a std::string
.
Here's my procedure definition (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
And here's a working example of how to use them (main.cpp
):
#include <iostream>
#include "procedure.hpp"
Procedure run() {
// NOTE: message is an lvalue
Message message{"message lifetime spans co_yield"};
co_yield message;
}
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();
}
}
}
When I compile and run this, I get the following output (which is expected):
$ g++-12 main.cpp -fcoroutines
$ ./a.out
[Message] Procedure started
[Message] message lifetime spans co_yield
[Message] Procedure finished
My compiler is GCC 12 on macOS:
g++-12 (Homebrew GCC 12.2.0) 12.2.0
However, if I change the definition of run
so the message yielded is a temporary (in main.cpp
):
Procedure run() {
// NOTE: message is a temporary
co_yield {"message is a temporary"};
}
Then the program crashes with an invalid free:
$ 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
As best as I can tell via debugging, the invalid free occurs during the destruction of the message's value
field. This is very unexpected to me since I have not done any memory management myself beyond std::string
does, and I copy the message during the yield.
The crash doesn't occur if the value
field has some other type (e.g. an integer). It also doesn't occur if I yield the value directly (instead of wrapping it in the Message
type).
What's going on here? How can I resolve this so I can safely yield a temporary?
Thanks to those who pointed out that it could be a compiler issue! After updating to the latest version of GCC (13), I no longer have an issue.