【问题标题】:Allocate aligned memory on the stack like _alloca在堆栈上分配对齐的内存,如 _alloca
【发布时间】:2018-04-03 09:18:39
【问题描述】:

_alloca() 的文档说here

_alloca 例程返回一个指向已分配空间的 void 指针, 这保证被适当地对齐以存储任何类型的 对象。

但是,here 它说:

_alloca 需要 16 字节对齐,另外还需要使用帧指针。

所以似乎在第一个参考文献中,他们忘记了 32 字节对齐的 AVX/AVX2 类型,例如 __m256d

让我感到困惑的另一件事是第一页说_alloca() 已弃用,而它建议改用一个可能从堆而不是堆栈分配内存的函数(这在我的多线程应用程序中是不可接受的) .

那么有人可以指出我是否有一些现代(也许是新的 C/C++ 标准?)方法来对齐堆栈内存分配?

澄清1:请不要提供要求数组大小为编译时常数的解决方案。我的函数根据运行时参数值分配可变数量的数组项。

【问题讨论】:

  • 首先,确定您要询问的是 C 还是 C++,尽管 _alloca 不是其中任何一个的一部分。
  • alloca 在 16 字节上对齐分配。如果您需要另一个对齐 - 分配更多内存并对齐自己
  • std::aligned_storage 能满足您的需求吗?您可以将对齐指定为第二个模板参数,它来自给定使用alignas 的示例实现的堆栈。 en.cppreference.com/w/cpp/types/aligned_storage
  • 什么是alignof(__m256d),为了那些没有你的平台扩展的人的利益?
  • @KerrekSB,问题是:32 字节。

标签: c++ memory-management c++17 c11 alloca


【解决方案1】:

使用 _alloca() 过度分配,然后手动对齐。像这样:

const int align = 32;
void *p =_alloca(n + align - 1);
__m256d *pm = (__m256d *)((((int_ptr_t)p + align - 1) / align) * align);

如有必要,将const 替换为#define

【讨论】:

  • 更好(__m256d *)(((UINT_PTR)p + (align - 1)) & ~(align - 1))
  • 两种方法都可以 :) 关键是,过度分配到最坏的情况 - (alignment-1) 额外字节。然后四舍五入。
  • 其实你只需要多分配16个字节,然后检查p % 32 != 0。如果它不是 0 添加 16 并完成。地址需要按照规范对齐 16 个字节。
  • 规范真的是这么说的吗?我认为这是一个 GCC 实现的怪癖。引用可能吗?
【解决方案2】:

_alloca() 当然不是处理堆栈对齐的标准或可移植方式。幸运的是,在 C++11 中,我们得到了 alignasstd::aligned_storage。这些都不会强迫您将任何东西放在堆上,因此它们应该适用于您的用例。例如,要将结构数组与 32 字节边界对齐:

#include <type_traits>

struct bar { int member; /*...*/ };
void fun() {
  std::aligned_storage<sizeof(bar), 32>::type array[16];
  auto bar_array = reinterpret_cast<bar*>(array);
}

或者,如果您只想将堆栈上的单个变量与边界对齐:

void bun() {
  alignas(32) bar b;
}

您还可以使用alignof 运算符来获取给定类型的对齐要求。

【讨论】:

    【解决方案3】:

    C++11 引入了alignof 运算符:

    一个 alignof 表达式产生其操作数类型的对齐要求。

    您可以按如下方式使用它:

    struct s {};
    typedef s __attribute__ ((aligned (64))) aligned_s;
    
    std::cout << alignof(aligned_s); // Outputs: 64
    

    注意:如果你的类型的对齐大于它的大小,编译器不会让你声明数组类型的数组(查看更多here):

    错误:数组元素的对齐方式大于元素大小

    但是,如果你的类型的对齐小于它的大小,你可以 安全分配数组:

    aligned_s arr[32];
    -- OR --
    constexpr size_t arr_size = 32;
    aligned_s arr[arr_size];
    

    支持 VLA 的编译器也将允许那些用于新定义的类型。

    【讨论】:

    • 这种方法是否允许非常量数组大小?在我需要_alloca()的函数调用之间,数组大小在运行时发生变化。
    • cl 不支持非常量数组大小
    • @SergeRogatch,动态阵列(又名 VLA)是 considered as part of the standard,但没有成功。不过,G++ (4.6.3) 和 Clang (900.0.38) 允许这样做。
    【解决方案4】:

    “现代”的方式是:

    Don't make variable-length allocation on the stack.

    在您的问题的上下文中 - 想要在堆上分配但避免这样做 - 我假设您分配的内存可能超过一些小的编译时常量内存。在这种情况下,您只需使用alloca() 调用来破坏您的堆栈。相反,使用线程安全的内存分配器。我确信 GitHub 上有这方面的库(最坏的情况下,您可以使用全局互斥锁来保护分配调用,尽管如果您需要大量它们,这会很慢)。

    另一方面,如果您事先知道分配大小的上限是多少 - 只需在线程本地存储中预先分配那么多内存即可;或使用固定大小的本地数组(将在堆栈上分配)。

    【讨论】:

    • 不要在堆栈上进行可变长度分配 - 这就是为什么?
    • 真正的可变长度分配在相对较小的块上非常有效。如果我们在用户模式和自己的 exe 文件中执行此操作(因此我们确切知道堆栈大小并可以在构建时设置它)。通常我们在堆栈中免费分配数十万字节。另一个问题,当我们第一次这样做并分配几个页面(4KB)或更多时,这将是慢慢比较堆分配(如果之前特别不移动保护页面)。如果定义了堆栈溢出行为。 (所有这些都适用于 windows
    猜你喜欢
    • 2013-08-10
    • 2012-09-17
    • 2011-06-25
    • 1970-01-01
    • 1970-01-01
    • 2018-07-24
    • 2011-05-28
    • 2018-09-17
    相关资源
    最近更新 更多