必须为 C++20 协程框架保留多少内存?

How much memory must be reserved for a C++20 coroutine frame?

首先,我想预测我的代码的内存使用情况,就像任何负责任的程序员都应该做的那样。即使我 不是 决定使用 placement new 分配我的协程框架,这也适用(见下面的伪代码)。 即使假设我改变了我对放置更新我所有协程的想法,因此我让编译器在堆上分配我所有的协程,我仍然希望 C++ 语言告诉我我有多少堆去吃那个。

但是,IRL,我的目标是高可靠性和嵌入式环境。甚至可能 堆,所以...

struct coroutine_return_type
{
  struct promise_type
  {
    void *operator new(std::size_t sz, char *buf, std::size_t szbuf)
    {
      if (sz > szbuf)
        throw std::bad_alloc{};
      return buf;
    }
    void operator delete(void *)
    {
    }
    // ...
  };
  // ...
};

coroutine_return_type my_coroutine(char *, std::size_t)
{
  // The arguments, char * and std::size_t,
  // have been fowrarded to promise_type::operator new
  // but here in the coroutine body they aren't used again...
  for ( ; ; )
    co_yield /* something */;
}

struct coroutine_instance_type
{
  char my_coroutine_frame[ /* WHAT? */ ];
  coroutine_return_type my_coroutine_instance;
  coroutine_instance_type()
    : my_coroutine_instance{my_coroutine(my_coroutine_frame, sizeof(my_coroutine_frame))}
  {
    // ...
  }
  // ...
};

我想要什么

我想要一个 return 协程大小上限的编译时表达式,以替换 /* WHAT? */.

愚蠢的解决方案

有一个明显愚蠢的方法(不完全)做我想做的事:

  1. 子类std::bad_alloc。然后我的operator new中的throw std::bad_alloc{}就变成了throw std::my_bad_alloc{sz}。 catch 块可以调用 my_bad_alloc_instance.get_parameter() 来了解 szoperator new.

    中的内容
  2. 调用 my_coroutine(nullptr, 0) 并捕获异常。

这有什么愚蠢的(非详尽列表):

它不是编译时表达式,因为它必须使用 throw 来“return”它的值,而 throw 永远不能用在编译时表达式中。但是在我的伪代码中替换 /* WHAT? */ 需要是一个编译时表达式。

这是一个样本,不是上限。假设协程帧的实际分配大小取决于 运行 时间的条件。 (现在,我预计不同 运行 时间条件下的协程大小不会实际出现在我的 IRL 应用程序中 ,但根据 C++ 标准,它似乎是可能的。)在这种情况下,仅了解实际传递给 operator new 的大小是不够的。所需的表达式必须 return,相反, 上限 可以 可以 传递给 operator new

所以,总结一下:

问题总结

C++语言提供了哪些查询协程帧大小的工具?理想的工具应该是用于将非堆内存分配给协程的编译时表达式,或者,同样的工具也可以用于限制堆的数量。

What tools does the C++ language provide to query the size of a coroutine frame?

None。你想要的是设计上不可能的。

co_await C++ 中协程的设计方式使得协程是函数的实现细节。仅仅从一个函数声明,不可能知道一个函数是否是一个协程,或者它是否恰好有一个可以使用各种协程机制的签名。该功能旨在以这样的方式工作,即无论函数是或不是协程,它都有效地 none 您的业务。

能够确定协程框架的大小首先需要能够识别协程。由于系统的设计使得这是不可能的......好吧,你就是这样。

在 C++20 协同程序的标准化过程中,对此进行了长时间的辩论。在优化器完成其工作之前,无法确定协程框架的布局和大小,并且要使该信息可用于前端将需要对所有现有编译器进行基本的重新架构。实施者报告说,甚至(有用的)上限都不可行。

请参阅 P1365R0 的第 2 部分和第 4 部分讨论在不允许动态内存分配的环境中使用协程的方法。

正如 Nicol Bolas 提到的那样,不可能将其作为 constexpr 值获取。但这对于“正常功能”也是不可能的。只有一条规则“不要在堆栈上存储大对象以避免堆栈溢出”。

根据经验,所需堆存储的最大值是在第一次继续之后必须可用的局部变量的大小,最终是一些小的“管理字段”来存储连接点(通常是一些一种 int)。

但我们的编译器现在非常聪明,可以完全优化堆分配 - 所以您不必太担心。