MiddleДебаггингИногдаЕщё не отвечали
Как корутина может утечь по памяти и как это предотвратить?
fibonacci() — бесконечный генератор-корутина. Под санитайзером утечек этот код сообщает об утёкшем coroutine frame на каждом вызове.
Generator fibonacci(); // бесконечный генератор, final_suspend = suspend_always
void buggy() {
std::coroutine_handle<promise_type> h = fibonacci().handle;
h.resume();
}
Найдите ошибку и объясните причину.
coroutine_handle не владеет фреймом. Если потерять хэндл незавершённой корутины или не вызвать destroy(), фрейм утекает. Предотвращается обёрткой хэндла в RAII-тип, чей деструктор вызывает destroy(), и корректной move-семантикой, чтобы владение оставалось уникальным.
- ✗Считать, что
coroutine_handleвладеет фреймом или считает на него ссылки - ✗Копировать хэндл так, что оба владельца вызывают
destroy()— двойное освобождение - ✗Думать, что
final_suspendсsuspend_alwaysсам выполняет очистку
- →Что пойдёт не так, если
final_suspendвернётsuspend_never? - →Как обнаружить утёкший coroutine frame с помощью санитайзера?
Оглавление
Утечка: потерянный хэндл
Generator fibonacci(); // бесконечный генератор, final_suspend = suspend_always
void buggy() {
std::coroutine_handle<promise_type> h = fibonacci().handle;
h.resume();
// h уходит из области видимости — destroy() не вызван.
// Фрейм бесконечной корутины НИКОГДА не освобождается → утечка.
}
coroutine_handle — это просто указатель. Он не считает ссылки и не вызывает destroy() автоматически. Если корутина не завершилась (или final_suspend вернул suspend_always), фрейм живёт, пока кто-то явно не вызовет destroy().
Решение: RAII-обёртка с уникальным владением
struct Generator {
std::coroutine_handle<promise_type> handle;
Generator(Generator&& o) noexcept
: handle(std::exchange(o.handle, {})) {} // передаём владение
Generator(const Generator&) = delete; // запрещаем копирование
~Generator() { if (handle) handle.destroy(); } // фрейм освобождается ровно один раз
};
Ключевые правила:
- деструктор обёртки вызывает
destroy()— фрейм освобождается детерминированно; - копирование удалено, move обнуляет источник — нет двойного
destroy(); - проверка
if (handle)защищает от вызова на пустом хэндле после move.
Оглавление