我们可以在 c++20 协程中使用 alloca() 或可变长度数组扩展吗?

问题描述 投票:0回答:2

GCC C++ 编译器(还有许多其他 C++ 编译器)提供非标准扩展,例如

  • alloca()
    用于基于堆栈的分配
  • 可变长度数组,因为它们是 C 标准的一部分

从基本角度来看,这些可以在 C++20 协程内部使用吗?有可能吗?如果是的话,这是如何实现的?

据我了解,C++20 协程通常在第一次调用时(即创建 Promise 对象时)为协程创建堆栈帧,因此需要知道协程堆栈帧的大小。

但是,这与 alloca 或其他运行时动态堆栈分配不能很好地配合。

那么这是否可能?如果可能,如何实施?或者说有什么影响?

c++ g++ c++20 c++-coroutine alloca
2个回答
4
投票

不幸的是,

alloca
与GCC中的C++20协程不兼容。最糟糕的是编译器不会对此发出警告。

此代码示例演示了不兼容性:

#include <coroutine>
#include <iostream>

struct ReturnObject {
  struct promise_type {
    unsigned * value_ = nullptr;

    void return_void() {}
    ReturnObject get_return_object() {
      return {
        .h_ = std::coroutine_handle<promise_type>::from_promise(*this)
      };
    }
    std::suspend_never initial_suspend() { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void unhandled_exception() {}
  };

  std::coroutine_handle<promise_type> h_;
  operator auto() const { return h_; }
};

template<typename PromiseType>
struct GetPromise {
  PromiseType *p_;
  bool await_ready() { return false; }
  bool await_suspend(std::coroutine_handle<PromiseType> h) {
    p_ = &h.promise();
    return false;
  }
  PromiseType *await_resume() { return p_; }
};

ReturnObject counter()
{
  auto pp = co_await GetPromise<ReturnObject::promise_type>{};

  //unsigned a[1]; auto & i = a[0]; //this version works fine
  auto & i = *new (alloca(sizeof(unsigned))) unsigned(0); //and this not
  for (;; ++i) {
    pp->value_ = &i;
    co_await std::suspend_always{};
  }
}

int main()
{
  std::coroutine_handle<ReturnObject::promise_type> h = counter();
  auto &promise = h.promise();
  for (int i = 0; i < 5; ++i) {
    std::cout << *promise.value_ << std::endl;
    h();
  }
  h.destroy();
}

https://gcc.godbolt.org/z/8zG446Esx

它应该打印

0 1 2 3 4
,但由于使用了
alloca

而并非如此

0
投票

是的,从技术上来说。

alloca
的部分行为是在函数返回时、当前堆栈帧被弹出时释放分配给它的任何内存,就像您可能知道的其他堆栈内存一样。

C++ 协程是无堆栈的。创建协程时,它会像结构一样在堆上分配所有堆栈变量(可能会省略)。这就是协程的“堆栈框架”所在的地方,称为协程框架。

alloca
不会增加协程框架的大小或向其添加变量。它们的实际堆栈帧是临时创建的,并且仅存在于恢复和下一个挂起点之间。在恢复和挂起之间,它们的行为就像常规函数一样。虽然您存储到
alloca
分配的指针可能是持久的,因为它位于协程帧中,但分配本身不是,因为它位于挂起时弹出的当前堆栈帧中。

也就是说,从技术上讲,您可以使用它直到协程挂起,但可能不会按照您预期的方式使用。之后,即使在同一个协程中,数据也会变成垃圾,就像常规堆栈一样。我仅在 GCC 和 Clang 上进行了测试。

我不确定与协程堆省略交互的影响。这还假设

alloca
的实现都以相同的方式工作,因为它是非标准的并且不提供任何保证。也不能保证这会永远有效,但今天确实有效。

我也不知道有什么方法可以在运行时增加协程帧大小。

© www.soinside.com 2019 - 2024. All rights reserved.