C++ 协程:从最终挂起点调用“handle.destroy”是否有效?

Vog*_*ang 5 c++ clang address-sanitizer c++20 c++-coroutine

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

根据我的理解,这应该没问题,因为协程当前已暂停并且不会再次恢复。

尽管如此,AddressSanitizer 仍会报告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\n";
    co_return;
}

int main() {
   auto x = foo();
}
Run Code Online (Sandbox Code Playgroud)

当使用 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中找到完整的代码)

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

Dav*_*aim 3

如果您 100% 确定之后没有人会使用协程 Promise,那么它是完全有效的。调用coroutine_handle::destroy相当于调用协程 Promise 析构函数。

既然如此,那当初为什么要这样做呢?刚刚std::suspend_neverfinal_suspend

std::suspend_never final_suspend() const noexcept { return {}; }
Run Code Online (Sandbox Code Playgroud)

它相当于你的代码。final_suspend如果我们想在协程完成后用协程 Promise 做一些有意义的事情,比如返回协程存储的结果,那么我们想要挂起协程。由于您的task对象不存储或返回任何内容,我不明白为什么要最终挂起它。

请注意,如果您使用第三方库,例如我的concurrencpp,您需要确保可以销毁不属于您的承诺。协程 Promise 可能会被挂起,但仍被其他地方引用coroutine_handle。这又回到了第一点。就我的库而言,它是不安全的,因为对象可能result仍然引用它。

总之,在以下情况下可以调用coroutine_promise::destroy

  1. 协程被挂起(当你到达final_suspend它时)
  2. 销毁后没有人会使用该协程承诺(特别确保没有类似 future 的对象引用该协程!)
  3. destroy之前没有被调用过(双删除)