在堆栈上分配任意常量大小的结构

Allocating structs of arbitrary constant size on the stack

我写了一个小型的工作插件服务器。插件是使用 .so 共享 objects 实现的,它们在运行时通过调用 dlopen (header <dlfcn.h>) 在“服务器”中手动加载。

所有共享的 object 插件具有相同的界面:

extern "C" void* do_something() {
    return SharedAllocator<T>{}.allocate(...); // new T
}
extern "C" size_t id = ...; // unique

服务器负责动态加载和读取.so二进制文件的符号。所有 .so 插件都可以通过服务器二进制文件中定义的方法 do_something_proxy 相互调用,该方法充当调用者和被调用者之间的粘合剂:

extern "C" void* do_something_proxy(size_t id) {
    // find the requested handle
    auto handle = some_so_map.find(id)->second;

    // call the handle's `do_something`
    void* something_done = handle.do_something();

    // forward the result
    return something_done;
}

为了稍微简化一下,我们假设 some_so_map 是一个普通的 std::unordered_map<size_t, so_handle_t>,在执行代理时使用对 dlopen 的一系列调用来填充。

我的问题是 do_something_proxy 的每个调用者在编译时都知道 T。正如我之前所说,T 可能因调用站点而异;然而 T never 改变任意调用站点。

供参考,这里是 all 呼叫者使用的定义:

template <typename T, size_t id>
T* typed_do_soemthing_proxy() {
    // simple cast of the proxy
    return reinterpret_cast<T*>(do_soemthing_proxy(id));
}

换句话说,do_something_proxy 对于某些任意插件 id 总是具有相同的 return 类型。

如果不是代理,我可以只模板 do_soemthing_proxy 并传递 Tstd::array<int8_t, N>sizeof(T) == N,并将不必要的内存分配给确保 T 在调用 do_something_proxy 可以移动到堆栈时不被切片。但是,代理无法在编译期间了解所有可能的 return 类型,并且无法导出 do_something_proxy.

的无数版本

所以我的问题是do_soemthing_proxy有没有办法在其堆栈上分配T的有效大小(即使用alloca 或其他形式的堆栈分配)?

据我所知,alloca 在这里似乎不起作用,因为 do_soemthing_proxy 只能从所请求插件的 do_something 函数接收单个值。 do_soemthing_proxy 将同时接收要分配的大小和要复制到已分配内存的字节数。如果只有 alloca 可以在两者之间被“压扁”...

我知道我可以使用 std::array<int8_t, N> 在堆栈上分配固定数量的内存,其中 N 的值为 256 甚至 1024。但是,这个解决方案有点脏。它不必要地将数据从一个堆栈框架复制到另一个堆栈框架,并限制了插件可以 return 的数据量。最重要的是,(虽然我还没有对这个解决方案进行基准测试)除非编译器可以跨越动态边界删除副本,我假设复制 1024 字节比复制即 sizeof(std::string) 字节更多的工作。

在理想情况下,我相信 do_soemthing_proxy 应该 return 一个用 RAII 处理这个问题的结构。 const std::any 即 stack-allocated,如果你愿意的话。这可能吗?

如果这在 c++ 中根本不可能,是否可以在汇编中以可移植的方式实现此行为,即通过手动劫持堆栈或基指针?

谢谢。

其实,我刚刚找到了解决办法。归结为反转分配 T 的内存位置的方向。

Is there any way for do_soemthing_proxy to allocate the effective size of T on its stack?

也许吧。但代码实际需要的是在调用者位置分配 T 的有效大小,而不是在代理内部。由于调用者知道 sizeof(T),您所要做的就是在调用者 调用 之前在调用者的堆栈上为 T 分配 space 19=],然后在调用的时候把分配的buffer的地址传给do_something_proxy

来电者:

template <typename T, size_t id>
T typed_do_something_proxy() {
    std::aligned_storage_t<sizeof(T), alignof(T)> return_buffer;
    do_something_proxy(id, &return_buffer);
    return *std::launder(reinterpret_cast<T*>(&return_buffer));
}

对于代理:

extern "C" void do_something_proxy(size_t id, void* return_buffer) {
    auto handle = some_so_map.find(id)->second;
    handle.do_something(return_buffer);
}

对于被叫方

extern "C" void do_something(void* return_buffer) {
    new(return_buffer) T(...); // placement new
}