c++协程连续循环导致栈溢出
c++ coroutine continuous loop causes stack overflow
我在visual studio循环中的协程中遇到堆栈溢出异常,发现循环有一个错误阻止它终止但我想知道为什么堆栈溢出?协程甚至可能没有使用堆栈,而是使用堆,即使使用了堆栈,也根本没有任何递归调用
经过一些实验,我可以重现崩溃:
- msvc 19.28
- wsl 和 mingw64 上的 g++-10
- clang-cl windows 上的 10 和 linux
上的 clang++-10
此代码导致堆栈溢出:
#include <stdexcept>
#include <utility>
#include <cstdio>
#ifdef __clang__
#ifdef _WIN32
#pragma message "using clang coroutine header"
#include "clang-cl-coro.h"
#else
#pragma message "using coroutine experimental header"
#include <experimental/coroutine>
#endif
namespace std
{
template<class P = void>
using coroutine_handle = experimental::coroutine_handle<P>;
using suspend_never = experimental::suspend_never;
using suspend_always = experimental::suspend_always;
}
#else
#pragma message "using coroutine header"
#include <coroutine>
#endif
class vtask
{
inline static size_t task_count = 0;
public:
struct promise_type
{
inline static size_t promise_count = 0;
std::coroutine_handle<> waiter;
std::exception_ptr ex_ptr = nullptr;
struct resume_waiter
{
inline static size_t awaiter_count = 0;
std::coroutine_handle<> waiter;
resume_waiter(std::coroutine_handle<> waiter) noexcept : waiter{ waiter }
{
++awaiter_count;
printf("[%zu] resume_waiter(std::coroutine_handle<> waiter)\n", awaiter_count);
}
~resume_waiter()
{
--awaiter_count;
printf("[%zu] ~resume_waiter()\n", awaiter_count);
}
bool await_ready() const noexcept { return false; }
auto await_suspend(std::coroutine_handle<>) noexcept
{
return waiter;
}
void await_resume() const noexcept {}
};
promise_type()
{
++promise_count;
printf("[%zu] vtask::promise_type()\n", promise_count);
}
~promise_type()
{
--promise_count;
printf("[%zu] ~vtask::promise_type()\n", promise_count);
}
vtask get_return_object() { return { *this }; }
constexpr std::suspend_always initial_suspend() noexcept { return {}; }
resume_waiter final_suspend() const noexcept { return { waiter }; }
void unhandled_exception() noexcept
{
ex_ptr = std::current_exception();
}
void return_void() const noexcept {}
};
vtask(promise_type& p) : coro{ std::coroutine_handle<promise_type>::from_promise(p) }
{
++task_count;
printf("[%zu] vtask(promise_type& p)\n", task_count);
}
vtask(vtask&& other) noexcept : coro{ std::exchange(other.coro, nullptr) }
{
++task_count;
printf("[%zu] vtask(vtask&& other)\n", task_count);
}
~vtask()
{
if (coro)
coro.destroy();
--task_count;
printf("[%zu] ~vtask()\n", task_count);
}
bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
coro.resume();
}
void await_resume() noexcept {}
private:
std::coroutine_handle<promise_type> coro;
};
struct detached_task
{
struct promise_type
{
inline static size_t promise_count = 0;
promise_type()
{
++promise_count;
printf("[%zu] detached_task::promise_type()\n", promise_count);
}
~promise_type()
{
--promise_count;
printf("[%zu] ~detached_task::promise_type()\n", promise_count);
}
detached_task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept
{
std::terminate();
}
constexpr void return_void() const noexcept {}
};
inline static size_t task_count = 0;
detached_task()
{
++task_count;
printf("[%zu] detached_task()\n", task_count);
}
~detached_task()
{
--task_count;
printf("[%zu] ~detached_task()\n", task_count);
}
};
vtask do_Whosebug() { co_return; }
detached_task Whosebug()
{
for (;;)
co_await do_Whosebug();
}
int main()
{
Whosebug();
}
使用的命令行:
cl /std:c++latest coro-Whosebug.cpp /EHsc
for msvc
g++ -std=c++20 coro-Whosebug.cpp -fcoroutines
用于 mingw64
clang-cl /std:c++latest coro-Whosebug.cpp /EHsc
g++-10 -std=c++20 coro-Whosebug.cpp -fcoroutines -o overflow.bug
在 wsl
clang++-10 -std=c++20 -stdlib=libc++ coro-Whosebug.cpp -o overflow-clang.bug
在 wsl
这是 windows 上的 clang coro header:
#pragma once
namespace std { namespace experimental { inline namespace coroutines_v1 {
template <typename R, typename...> struct coroutine_traits {
using promise_type = typename R::promise_type;
};
template <typename Promise = void> struct coroutine_handle;
template <> struct coroutine_handle<void> {
static coroutine_handle from_address(void *addr) noexcept {
coroutine_handle me;
me.ptr = addr;
return me;
}
void operator()() { resume(); }
void *address() const { return ptr; }
void resume() const { __builtin_coro_resume(ptr); }
void destroy() const { __builtin_coro_destroy(ptr); }
bool done() const { return __builtin_coro_done(ptr); }
coroutine_handle &operator=(decltype(nullptr)) {
ptr = nullptr;
return *this;
}
coroutine_handle(decltype(nullptr)) : ptr(nullptr) {}
coroutine_handle() : ptr(nullptr) {}
// void reset() { ptr = nullptr; } // add to P0057?
explicit operator bool() const { return ptr; }
protected:
void *ptr;
};
template <typename Promise> struct coroutine_handle : coroutine_handle<> {
using coroutine_handle<>::operator=;
static coroutine_handle from_address(void *addr) noexcept {
coroutine_handle me;
me.ptr = addr;
return me;
}
Promise &promise() const {
return *reinterpret_cast<Promise *>(
__builtin_coro_promise(ptr, alignof(Promise), false));
}
static coroutine_handle from_promise(Promise &promise) {
coroutine_handle p;
p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true);
return p;
}
};
template <typename _PromiseT>
bool operator==(coroutine_handle<_PromiseT> const& _Left,
coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Left.address() == _Right.address();
}
template <typename _PromiseT>
bool operator!=(coroutine_handle<_PromiseT> const& _Left,
coroutine_handle<_PromiseT> const& _Right) noexcept
{
return !(_Left == _Right);
}
template <typename _PromiseT>
bool operator==(coroutine_handle<_PromiseT> const& _Left,
std::nullptr_t) noexcept
{
return _Left.address() == nullptr;
}
template <typename _PromiseT>
bool operator==(std::nullptr_t, coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Right.address() == nullptr;
}
template <typename _PromiseT>
bool operator!=(coroutine_handle<_PromiseT> const& _Left,
std::nullptr_t) noexcept
{
return !(_Left == nullptr);
}
template <typename _PromiseT>
bool operator!=(std::nullptr_t, coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Right.address() != nullptr;
}
struct suspend_always {
bool await_ready() { return false; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
struct suspend_never {
bool await_ready() { return true; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
}}}
在 windows 上,msvc 和 clang-cl 构建早期发生崩溃,但 mingw64 和 wsl 构建需要更多时间
我使用的 gcc 10.1 似乎有一个已知的错误,其中任务构造了两次,但一旦每次迭代都泄漏了一个任务就被销毁,这似乎导致了溢出
但是 clang 和 msvc 没有这个错误,它们也会崩溃!
编辑:尝试了 gcc 10.3 mingw64,它没有提到的 gcc 错误,但它也会导致堆栈溢出!有问题的编译器甚至更快!也许这种行为是预期的?
我搞不懂上面的代码哪里错了
问题出在这部分代码中:
void await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
coro.resume();
}
这种不对称传输导致计算器溢出并将代码更改为:
auto await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
return coro;
}
解决了 clang 和 msvc 的问题,但 gcc 10.3 仍然崩溃,我认为它还不支持对称传输
我在visual studio循环中的协程中遇到堆栈溢出异常,发现循环有一个错误阻止它终止但我想知道为什么堆栈溢出?协程甚至可能没有使用堆栈,而是使用堆,即使使用了堆栈,也根本没有任何递归调用
经过一些实验,我可以重现崩溃:
- msvc 19.28
- wsl 和 mingw64 上的 g++-10
- clang-cl windows 上的 10 和 linux 上的 clang++-10
此代码导致堆栈溢出:
#include <stdexcept>
#include <utility>
#include <cstdio>
#ifdef __clang__
#ifdef _WIN32
#pragma message "using clang coroutine header"
#include "clang-cl-coro.h"
#else
#pragma message "using coroutine experimental header"
#include <experimental/coroutine>
#endif
namespace std
{
template<class P = void>
using coroutine_handle = experimental::coroutine_handle<P>;
using suspend_never = experimental::suspend_never;
using suspend_always = experimental::suspend_always;
}
#else
#pragma message "using coroutine header"
#include <coroutine>
#endif
class vtask
{
inline static size_t task_count = 0;
public:
struct promise_type
{
inline static size_t promise_count = 0;
std::coroutine_handle<> waiter;
std::exception_ptr ex_ptr = nullptr;
struct resume_waiter
{
inline static size_t awaiter_count = 0;
std::coroutine_handle<> waiter;
resume_waiter(std::coroutine_handle<> waiter) noexcept : waiter{ waiter }
{
++awaiter_count;
printf("[%zu] resume_waiter(std::coroutine_handle<> waiter)\n", awaiter_count);
}
~resume_waiter()
{
--awaiter_count;
printf("[%zu] ~resume_waiter()\n", awaiter_count);
}
bool await_ready() const noexcept { return false; }
auto await_suspend(std::coroutine_handle<>) noexcept
{
return waiter;
}
void await_resume() const noexcept {}
};
promise_type()
{
++promise_count;
printf("[%zu] vtask::promise_type()\n", promise_count);
}
~promise_type()
{
--promise_count;
printf("[%zu] ~vtask::promise_type()\n", promise_count);
}
vtask get_return_object() { return { *this }; }
constexpr std::suspend_always initial_suspend() noexcept { return {}; }
resume_waiter final_suspend() const noexcept { return { waiter }; }
void unhandled_exception() noexcept
{
ex_ptr = std::current_exception();
}
void return_void() const noexcept {}
};
vtask(promise_type& p) : coro{ std::coroutine_handle<promise_type>::from_promise(p) }
{
++task_count;
printf("[%zu] vtask(promise_type& p)\n", task_count);
}
vtask(vtask&& other) noexcept : coro{ std::exchange(other.coro, nullptr) }
{
++task_count;
printf("[%zu] vtask(vtask&& other)\n", task_count);
}
~vtask()
{
if (coro)
coro.destroy();
--task_count;
printf("[%zu] ~vtask()\n", task_count);
}
bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
coro.resume();
}
void await_resume() noexcept {}
private:
std::coroutine_handle<promise_type> coro;
};
struct detached_task
{
struct promise_type
{
inline static size_t promise_count = 0;
promise_type()
{
++promise_count;
printf("[%zu] detached_task::promise_type()\n", promise_count);
}
~promise_type()
{
--promise_count;
printf("[%zu] ~detached_task::promise_type()\n", promise_count);
}
detached_task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept
{
std::terminate();
}
constexpr void return_void() const noexcept {}
};
inline static size_t task_count = 0;
detached_task()
{
++task_count;
printf("[%zu] detached_task()\n", task_count);
}
~detached_task()
{
--task_count;
printf("[%zu] ~detached_task()\n", task_count);
}
};
vtask do_Whosebug() { co_return; }
detached_task Whosebug()
{
for (;;)
co_await do_Whosebug();
}
int main()
{
Whosebug();
}
使用的命令行:
cl /std:c++latest coro-Whosebug.cpp /EHsc
for msvc
g++ -std=c++20 coro-Whosebug.cpp -fcoroutines
用于 mingw64
clang-cl /std:c++latest coro-Whosebug.cpp /EHsc
g++-10 -std=c++20 coro-Whosebug.cpp -fcoroutines -o overflow.bug
在 wsl
clang++-10 -std=c++20 -stdlib=libc++ coro-Whosebug.cpp -o overflow-clang.bug
在 wsl
这是 windows 上的 clang coro header:
#pragma once
namespace std { namespace experimental { inline namespace coroutines_v1 {
template <typename R, typename...> struct coroutine_traits {
using promise_type = typename R::promise_type;
};
template <typename Promise = void> struct coroutine_handle;
template <> struct coroutine_handle<void> {
static coroutine_handle from_address(void *addr) noexcept {
coroutine_handle me;
me.ptr = addr;
return me;
}
void operator()() { resume(); }
void *address() const { return ptr; }
void resume() const { __builtin_coro_resume(ptr); }
void destroy() const { __builtin_coro_destroy(ptr); }
bool done() const { return __builtin_coro_done(ptr); }
coroutine_handle &operator=(decltype(nullptr)) {
ptr = nullptr;
return *this;
}
coroutine_handle(decltype(nullptr)) : ptr(nullptr) {}
coroutine_handle() : ptr(nullptr) {}
// void reset() { ptr = nullptr; } // add to P0057?
explicit operator bool() const { return ptr; }
protected:
void *ptr;
};
template <typename Promise> struct coroutine_handle : coroutine_handle<> {
using coroutine_handle<>::operator=;
static coroutine_handle from_address(void *addr) noexcept {
coroutine_handle me;
me.ptr = addr;
return me;
}
Promise &promise() const {
return *reinterpret_cast<Promise *>(
__builtin_coro_promise(ptr, alignof(Promise), false));
}
static coroutine_handle from_promise(Promise &promise) {
coroutine_handle p;
p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true);
return p;
}
};
template <typename _PromiseT>
bool operator==(coroutine_handle<_PromiseT> const& _Left,
coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Left.address() == _Right.address();
}
template <typename _PromiseT>
bool operator!=(coroutine_handle<_PromiseT> const& _Left,
coroutine_handle<_PromiseT> const& _Right) noexcept
{
return !(_Left == _Right);
}
template <typename _PromiseT>
bool operator==(coroutine_handle<_PromiseT> const& _Left,
std::nullptr_t) noexcept
{
return _Left.address() == nullptr;
}
template <typename _PromiseT>
bool operator==(std::nullptr_t, coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Right.address() == nullptr;
}
template <typename _PromiseT>
bool operator!=(coroutine_handle<_PromiseT> const& _Left,
std::nullptr_t) noexcept
{
return !(_Left == nullptr);
}
template <typename _PromiseT>
bool operator!=(std::nullptr_t, coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Right.address() != nullptr;
}
struct suspend_always {
bool await_ready() { return false; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
struct suspend_never {
bool await_ready() { return true; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
}}}
在 windows 上,msvc 和 clang-cl 构建早期发生崩溃,但 mingw64 和 wsl 构建需要更多时间
我使用的 gcc 10.1 似乎有一个已知的错误,其中任务构造了两次,但一旦每次迭代都泄漏了一个任务就被销毁,这似乎导致了溢出
但是 clang 和 msvc 没有这个错误,它们也会崩溃!
编辑:尝试了 gcc 10.3 mingw64,它没有提到的 gcc 错误,但它也会导致堆栈溢出!有问题的编译器甚至更快!也许这种行为是预期的?
我搞不懂上面的代码哪里错了
问题出在这部分代码中:
void await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
coro.resume();
}
这种不对称传输导致计算器溢出并将代码更改为:
auto await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
return coro;
}
解决了 clang 和 msvc 的问题,但 gcc 10.3 仍然崩溃,我认为它还不支持对称传输