函数包装中的堆栈分配/函数中的alloca

Stack allocation in function wrapper / alloca in function

本文关键字:函数 alloca 分配 堆栈 包装      更新时间:2023-10-16

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

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

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


指出:

最终目标是类似于std::vector的东西,它严格地在直接函数堆栈上工作。显然,它只能作为一个const对象传递给调用者,它的生命周期随着函数的结束而结束。

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

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

答案:很可能这是不可能的,只有宏方法才能工作

你不能。
当函数返回时,它的堆栈被展开,堆栈指针返回到它之前的位置。必须这样,如果你不想弄得一团糟的话。alloca所做的只是移动堆栈指针,所以函数return会取消这个分配。
宏可以工作,因为它们只是向同一个函数添加代码。但这将是丑陋的,没有真正改善的希望。

使用堆栈分配的主要好处可能是绕过了标准库malloc/new allocator。在这种情况下,使用堆栈并不是唯一的选择。

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

当然,我最大的障碍是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;
    }
};

您始终可以实现您自己的自定义分配器,它将与线程堆栈一样高效。从我的经验来看,alloca是非常危险的,如果隐藏在某些c++类层次结构中(例如。

下面是一个用于分配堆栈上数组的示例宏,它通过helper内联函数尽可能地利用了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

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

此外,alloca实际上只适用于c++中的pod类型,而容器绝对不是。

EDIT:这个问题的答案部分地解决了这个问题:它从堆栈中为vector分配初始存储,但如果需要更多的容量,则从堆中分配。