Выделить в стеке выровненную память, например _alloca

В документации для _alloca() написано здесь:

Подпрограмма _alloca возвращает пустой указатель на выделенное пространство, которое гарантированно будет соответствующим образом выровнено для хранения любого типа объекта.

Однако здесь говорится:

_alloca должен быть выровнен по 16 байт и дополнительно требуется для использования указателя кадра.

Похоже, что в первой ссылке они забыли о 32-байтовых выровненных типах AVX / AVX2, таких как __m256d.

Еще меня смущает то, что на первой странице говорится, что _alloca() устарел, а вместо этого предлагается использовать функцию, которая может выделять память из кучи, а не из стека (что недопустимо в моем многопоточном приложении).

Так может ли кто-нибудь указать мне, есть ли какой-нибудь современный (возможно, новый стандарт C / C ++?) Способ выровненного распределения памяти стека?

Уточнение 1. Не предоставляйте решения, требующие, чтобы размер массива был постоянным во время компиляции. Моя функция выделяет переменное количество элементов массива в зависимости от значения параметра времени выполнения.


person Serge Rogatch    schedule 22.10.2017    source источник
comment
Во-первых, решите, спрашиваете ли вы о C или C ++, хотя _alloca не является их частью.   -  person    schedule 22.10.2017
comment
alloca выровняйте выделение на 16 байт. если вам нужно другое выравнивание - выделите больше памяти и выровняйте себя   -  person RbMm    schedule 22.10.2017
comment
Подойдет ли std::aligned_storage для ваших нужд? Вы можете указать выравнивание в качестве второго параметра шаблона, и оно поступает из стека, учитывая пример реализации, в которой используется alignas. en.cppreference.com/w/cpp/types/aligned_storage   -  person Joe    schedule 22.10.2017
comment
Что такое alignof(__m256d) для людей, у которых нет расширений вашей платформы?   -  person Kerrek SB    schedule 22.10.2017
comment
@KerrekSB, было в вопросе: 32 байта.   -  person Serge Rogatch    schedule 22.10.2017
comment
@SergeRogatch: Да, конечно, но это буквально значение выражения alignof или что-то, что вы получили откуда-то еще?   -  person Kerrek SB    schedule 22.10.2017
comment
если вам нужно выделить 32 байта allign памяти размером cb - PBYTE pb = (PBYTE)alloca(31 + cb); pb = (PBYTE)((ULONG_PTR)(pb + 31) & ~31);   -  person RbMm    schedule 23.10.2017
comment
@SergeRogatch Вы уверены, что первая ссылка неверна? Они совершенно последовательны. Что-то, что гарантированно соответствует любому типу, также может быть гарантированно выровнено только по 16-байтовой границе, потому что могут существовать машины, у которых нет 32-байтовых выровненных типов. Очевидно, что на машинах, у которых есть типы с требованием 32-байтового выравнивания, это будет 32-байтовое выравнивание.   -  person David Schwartz    schedule 23.10.2017
comment
@KerrekSB, перепроверил с std::cout << alignof(__m256d) << std::endl;, 32.   -  person Serge Rogatch    schedule 23.10.2017
comment
@Joe, нет, std::aligned_storage не кажется подходящим, потому что он требует, чтобы длина массива была постоянной во время компиляции.   -  person Serge Rogatch    schedule 23.10.2017
comment
Хорошо, тогда это расширенное выравнивание, и поддержка этого определяется реализацией. max_align_t дает только самое большое нерасширенное выравнивание, поэтому фраза для любого типа не совсем точна. Вы можете использовать вспомогательную функцию std::align для выравнивания памяти вручную.   -  person Kerrek SB    schedule 23.10.2017


Ответы (4)


Увеличьте доступность с помощью _alloca (), затем выровняйте вручную. Нравится:

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

При необходимости замените const на #define.

person Seva Alekseyev    schedule 22.10.2017
comment
лучше (__m256d *)(((UINT_PTR)p + (align - 1)) & ~(align - 1)) - person RbMm; 23.10.2017
comment
Любой из них работает :) Дело в том, что превышение доступности в худшем случае - (alignment-1) лишних байтов. Затем округлите. - person Seva Alekseyev; 23.10.2017
comment
На самом деле вам нужно выделить только 16 байт, а затем проверить p % 32 != 0. Если не 0, добавьте 16 и готово. Адрес должен быть выровнен по спецификации на 16 байт. - person Axel Gneiting; 13.07.2019
comment
Это действительно то, что написано в спецификации? Я думал, что это причуда реализации GCC. Цитировать может быть? - person Seva Alekseyev; 13.07.2019

_alloca(), безусловно, не является стандартным или переносимым способом обработки выравнивания в стеке. К счастью, в C ++ 11 мы получили alignas и std::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, чтобы получить требования к выравниванию для данного типа.

person Wijagels    schedule 24.10.2017

В C ++ 11 появился оператор alignof:

Выражение alignof дает требование выравнивания его типа операнда.

Вы можете использовать его следующим образом:

struct s {};
typedef s __attribute__ ((aligned (64))) aligned_s;

std::cout << alignof(aligned_s); // Outputs: 64

Примечание. Если выравнивание вашего типа больше, чем его размер, компилятор не позволит вам объявлять массивы типа массива (см. подробнее здесь):

ошибка: выравнивание элементов массива больше размера элемента

Но, если выравнивание вашего типа меньше его размера, вы можете спокойно выделять массивы:

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

Компиляторы, поддерживающие VLA, также позволят использовать их для нового определенного типа.

person Daniel Trugman    schedule 22.10.2017
comment
Допускает ли этот подход непостоянный размер массива? Размер массива изменяется во время выполнения между вызовами функции, где мне нужно _alloca(). - person Serge Rogatch; 22.10.2017
comment
cl не поддерживает непостоянный размер массива - person RbMm; 23.10.2017
comment
@SergeRogatch, динамические массивы (также известные как VLA) были считается частью стандарта, но не вошел в его состав. Хотя G ++ (4.6.3) и Clang (900.0.38) это позволяют. - person Daniel Trugman; 23.10.2017

«Современный» способ:

Не выделяйте переменную длину для стек.

В контексте вашего вопроса - желая выделить в куче, но воздерживаясь от этого - я предполагаю, что вы можете выделять больше, чем небольшой постоянный объем памяти во время компиляции. В этом случае вы просто разобьете свой стек этим вызовом alloca(). Вместо этого используйте поточно-ориентированный распределитель памяти. Я уверен, что для этого есть библиотеки на GitHub (и в худшем случае вы можете защитить вызовы выделения с помощью глобального мьютекса, хотя это медленно, если вам нужно их много).

С другой стороны, если вы заранее знаете, каков предел размера распределения - просто заранее выделите такой объем памяти в локальном хранилище потока; или используйте локальный массив фиксированного размера (который будет размещен в стеке).

person einpoklum    schedule 22.10.2017
comment
Не делайте выделения переменной длины в стеке - вот почему? - person RbMm; 23.10.2017
comment
действительно, распределение переменной длины очень эффективно для относительно небольших блоков. если мы сделаем это в пользовательском режиме и в собственном exe-файле (так что мы точно знаем размер стека и можем установить его во время сборки). обычно мы бесплатно выделяем в стеке сотни тысяч байт. Другой вопрос, когда мы делаем это в первый раз и выделяем несколько страниц (4 КБ) или более, это будет медленно сравнивать распределение кучи (если до этого не было специально перемещать охранную страницу вниз). и в случае, если определено поведение переполнения стека. (все это для windows) - person RbMm; 23.10.2017