C++协程:从最后一个挂起点调用`handle.delety`是否有效?

从C++协程的最终挂起中调用handle.destroy()是否有效?

根据我的理解,这应该很好,因为协程当前已暂停,不会再次恢复。

仍然,AddressSaniizer报告以下代码片段的heap-use-after-free

#include <experimental/coroutine>
#include <iostream>

using namespace std;

struct final_awaitable {
   bool await_ready() noexcept { return false; }
   void await_resume() noexcept {}
   template<typename PROMISE> std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<PROMISE> coro) noexcept {
      coro.destroy(); // Is this valid?
      return std::experimental::noop_coroutine();
   }
};

struct task {
   struct promise_type;
   using coro_handle = std::experimental::coroutine_handle<promise_type>;

   struct promise_type {
      task get_return_object() { return {}; }
      auto initial_suspend() { return std::experimental::suspend_never(); }
      auto final_suspend() noexcept { return final_awaitable(); }
      void unhandled_exception() { std::terminate(); }
      void return_void() {}
   };
};

task foo() {
    cerr << "foo
";
    co_return;
}

int main() {
   auto x = foo();
}

当使用clang 11.0.1和编译标志-stdlib=libc++ --std=c++17 -fcoroutines-ts -fno-exceptions -fsanitize=address编译时。(参见https://godbolt.org/z/eq6eoc)

(我实际代码的简化版本。您可以在https://godbolt.org/z/8Yadv1)

中找到完整的代码

这是我的代码中的问题还是AddressSaniizer中的错误肯定?


解决方案

如果您100%确定以后没有人会使用协程承诺,则它完全有效。调用coroutine_handle::destroy等同于调用协程承诺析构函数。

如果是这样的话,为什么要这样做呢?只需从final_suspend返回std::suspend_never

std::suspend_never final_suspend() const noexcept { return {}; }

它等同于您的代码。如果我们想在协程结束后对协程承诺做一些有意义的事情,比如返回协程的存储结果,那么我们希望在final_suspend中暂停协程。由于您的task对象不存储或返回任何内容,我看不出为什么要最终挂起它。

请注意,如果您使用第三方库,如我的concurrencpp,您需要确保可以破坏不属于您的承诺。协程承诺可能被挂起,但仍被其coroutine_handle在其他地方引用。回到第1点。对于我的库,它不安全,因为可能是result对象仍在引用它。

总之,可以调用coroutine_promise::destroyif:

  1. 协程暂停(当您达到final_suspend时)
  2. 销毁后没有人会使用该协程承诺(请特别确保不存在引用该协程、类似于未来的对象!)
  3. destroy之前未调用(双删除)

相关文章