【问题标题】:Stack allocation in function wrapper / alloca in function函数包装器中的堆栈分配/函数中的 alloca
【发布时间】:2012-01-28 13:53:09
【问题描述】:

我正在寻找一种将堆栈分配包装在抽象数据类型中的方法。例如,我想要一个可以通过堆栈上的分配严格工作的向量。当然,我最大的障碍是 alloca 只能在当前堆栈框架内工作——因此我看不到将其包装到函数中的简单方法。

到目前为止,我看到的唯一方法是使用类似宏的函数,这些函数可以保证编译到给定的堆栈帧中。我不喜欢这种方法,因为它不像人们希望的那样友好,并且需要比预期更冗长的命名。

无论如何我可以在其调用堆栈上分配一个函数吗?我知道这通常会破坏立即调用的堆栈,因此很可能该函数也必须以某种方式强制内联。我不清楚我有哪些选择,所以我正在寻找一些想法,或指向可能的选择的指针。


注意事项:

最终目标类似于std::vector,它严格适用于直接函数堆栈。显然它只会作为const 对象传递给被调用者,并且它的生命以函数结束。

只要 C 方法比我的基于宏的方法好,C 方法就可以了。虽然一些支持宏也是可以接受的。

我知道这是一个相当具体的优化,最理想的情况是我希望能够(使用标志)打开/关闭它(仅使用普通的 std::vector 进行调试)。它会给我们代码的重要部分带来轻微的速度提升,但可能不足以证明通过太多奇怪的构造使其不可读。

答案:很可能是不可能的,只有宏观方法才有效。

【问题讨论】:

  • 简而言之,你不能。 alloca 不能很好地与 C++ 对象模型配合使用。如果您想更严格地控​​制内存分配,您始终可以将自己的分配器用于标准容器。
  • 第一个链接是堆栈上的静态大小,我知道该怎么做,我希望有一个动态解决方案(我知道这可能是不可能的)。对于第二个问题,我不需要 STL 合规性,但这里的第一个答案可能相同(只是不可能)。
  • @DeadMG,为什么要去掉 C 标签?我表示我可以接受 C 方法——尤其是因为 C 解决方案比直接 C++ 解决方案更有可能。

标签: c++ gcc alloca


【解决方案1】:

这是一个分配堆栈数组的示例宏,它通过辅助内联函数尽可能利用 C++ 类型安全和编译时检查:

#include <type_traits>
#include <alloca.h>

namespace Utils {

// A wrapper for alloca which allocates an array of default-constructible, trivially-destructible objects on the stack
#define ALLOC_ON_STACK_ARRAY(T, nMembers) \
        ::Utils::InitArrayOfTriviallyDestructibles<T>(alloca(sizeof(T) * nMembers), size_t(nMembers))

// Helper function for ALLOC_ON_STACK_ARRAY() defined above. Initialize a block of memory as an array.
template <typename T>
inline T* InitArrayOfTriviallyDestructibles(void* p, size_t nMembers)
{
        static_assert(std::is_trivially_destructible<T>::value, "The type is not trivially destructible");
        return new (p) T[nMembers] {};
}

} // namespace Utils

【讨论】:

    【解决方案2】:

    您始终可以实现自己的自定义分配器,该分配器与线程堆栈一样高效。根据我的经验,alloca 可能非常危险,如果隐藏在某些 C++ 类层次结构中(即在 for 循环中)很容易破坏你的堆栈。

    【讨论】:

    • 在这种情况下最好使用 RAII,这样您的类似堆栈的存储就会被正确释放。
    【解决方案3】:

    当然,我最大的障碍是 alloca 只能在当前堆栈框架内工作——因此我看不到将它包装到函数中的简单方法。

    好吧,如果您只需要分配一次(也就是说,如果您有一个始终足够的最大容量),您可以在默认参数中调用 alloca:

    template<typename T, size_t Capacity>
    class stack_vector
    {
        T* start_;
        size_t size_;
    
    public:
    
        explicit stack_vector(void* memory = alloca(sizeof(T) * Capacity))
        {
            start_ = static_cast<T*>(memory);
            size_ = 0;
        }
    };
    

    【讨论】:

    • 默认参数的评估是否保证在调用者的堆栈框架内发生?至少在 gcc 中是这样。
    • 这没有多大意义。如果在编译时确实知道Capacity,您可以添加一个char data[Capacity] 成员。但是,如果您需要在运行时指定它,explicit stack_vector(size_t capacity, void* memory = alloca(sizeof(T) * capacity)) 不起作用(默认参数引用参数)。
    【解决方案4】:

    使用堆栈分配的主要好处可能是绕过标准库 malloc/new 分配器。鉴于此,使用堆栈并不是唯一的选择。

    堆栈分配的一种替代方法是使用基于mmap() 系统调用的自定义内存分配器。 mmap() 分配的内存可以用作自定义分配器的存储,而不是堆栈。为了避免调用mmap(),mmap() 分配的内存区域应该被缓存,例如,在全局线程特定变量中。

    【讨论】:

    • 恕我直言,基于堆栈的分配的主要好处是 O(1) 时间。
    • P.S.我编写了使用mmap 进行存储的容器类。设置和运行我的系统文件句柄很慢。
    • 是的,堆栈很可能是 hot 的,即在 CPU 数据缓存中。
    • @MikeDeSimone:我不认为所有基于mmap() 的分配器都会在文件句柄之外运行系统。
    • 也许不是,但它确实使它依赖于平台。我的经验是使用 Linux 2.6.9 左右;很久以前。
    【解决方案5】:

    堆栈确实不适合容器类使用的那种分配。例如,当一个向量需要扩展它的capacity 时,它很可能会分配一个新的存储区域,复制现有项目(因此 C++ 对容器中使用的对象的复制和默认构造函数的要求),并释放原始贮存。不用说,这对基于堆栈的存储造成了严重破坏,在函数退出之前无法释放。 (vector 可以在不复制的情况下就地扩展 capacity 的唯一方法是使用 realloc 函数,它没有 C++ 等效项,对您来说最重要的是,没有 alloca 等效项。)

    此外,alloca 仅适用于 C++ 中的 POD 类型,而容器绝对不是。

    编辑: The answer to this question 部分解决了问题:它从堆栈中为 vector 分配初始存储空间,但如果需要更多容量,则从堆中分配。

    【讨论】:

    • alloca 适用于在复杂对象类型上放置 new 。容器也可以自定义编写以支持此分配器,我不是在寻找用于 STL 或任何东西的插入式分配器。
    • 是的,对于我正在尝试优化的同一类型的事物,另一个问题是一个非常好的优化。事实上,这种类型的向量是我的下一步。我只是希望我能走得更远。
    • 它适用于placement new 的唯一原因是您已经计算了最终对象的实际存储大小。放置 new 实际上只是调用构造函数的一种方式。但是,您仍然必须从函数中调用alloca;你不能把它包装到另一个函数中。即使您可以强制 inline 始终内联,也不能保证一个 inline 函数的“堆栈”不会被以后的 inline 函数的“堆栈”重用。
    【解决方案6】:

    你不能。
    当一个函数返回时,它的堆栈被展开,堆栈指针回到它之前的位置。它必须,如果你不想要一个真正的混乱。 alloca 所做的只是移动堆栈指针,因此函数返回会撤消此分配。
    宏会起作用,因为它们只是将代码添加到同一个函数中。但这会很丑陋,没有真正的改进希望。

    【讨论】:

    • 如果 C++ 愿意,它可以跟踪堆栈中存储的对象,因此它可以像在异常处理期间展开时一样正确地销毁它们。当然,需要一些新的语法; alloca 不能开箱即用,原因与malloc 在涉及类时不能开箱即用。
    • 也许我只是在寻找类型安全的命名空间感知宏。 :)
    • 如果 C++ 想要,它可以把栈变成堆。但是,与堆相比,它有什么优势呢?
    猜你喜欢
    • 2014-07-20
    • 2016-04-14
    • 2023-03-30
    • 2012-02-27
    • 1970-01-01
    • 1970-01-01
    • 2012-08-01
    • 2016-10-24
    • 2016-05-28
    相关资源
    最近更新 更多