Распределение стека в функции-оболочке / alloca в функции - PullRequest
4 голосов
/ 27 декабря 2011

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

Пока что единственный способ сделать это - использовать макроподобные функции, которые гарантированно компилируются в заданный кадр стека. Мне не нравится этот подход, поскольку он не такой дружественный к типу, как можно было бы надеяться, и требует более подробного наименования, чем хотелось бы.

Могу ли я в любом случае получить функцию для размещения в стеке вызывающих программ? Я понимаю, что это обычно приводит к разрушению стека немедленного вызова, поэтому, скорее всего, функцию также придется как-то принудительно встроить. Мне не ясно, какие у меня есть варианты, поэтому я ищу некоторые идеи или указатели на возможные варианты.


Примечания:

Конечная цель - что-то вроде std::vector, которая работает строго в стеке непосредственных функций. Очевидно, что он будет передан только как const объект для вызываемых, и его жизнь заканчивается функцией.

Подход C хорош, если он лучше моего подхода на основе макросов. Хотя некоторые вспомогательные макросы также допустимы.

Я понимаю, что это довольно специфическая оптимизация, и оптимально я бы хотел иметь возможность (с флагом) включать / выключать ее (используя для отладки обычный std :: vector). Это дало бы незначительный прирост скорости для значительных частей нашего кода, но, вероятно, недостаточно, чтобы оправдать невозможность его чтения из-за слишком большого числа нечетных конструкций.

Ответ : Скорее всего, это невозможно, и будет работать только макроподход.

Ответы [ 6 ]

1 голос
/ 04 октября 2017

Вот пример макроса для выделения массива в стеке, который использует преимущества безопасности типов 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
1 голос
/ 28 декабря 2011

Вы всегда можете реализовать свой собственный распределитель, который будет столь же эффективен, как и стек потоков.Исходя из моего опыта, alloca может быть очень опасной, и если она спрятана в некоторых иерархиях классов C ++ (например, в цикле for), она может легко перевернуть ваш стек.

1 голос
/ 27 декабря 2011

Мое самое большое препятствие, конечно, заключается в том, что 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;
    }
};
1 голос
/ 27 декабря 2011

Основным преимуществом использования стекового выделения, вероятно, является обход стандартной библиотеки malloc / new allocator.В этом свете использование стека - не единственный вариант.

Одной альтернативой выделению стека является использование специального распределителя памяти на основе системного вызова mmap().Память, выделенная mmap(), может использоваться как хранилище для пользовательского распределителя вместо стека.Во избежание вызова mmap() часто область памяти, выделенная mmap(), должна кэшироваться, например, в глобальной переменной, специфичной для потока.

1 голос
/ 27 декабря 2011

Вы не можете.
Когда функция возвращается, ее стек разворачивается, и указатель стека возвращается туда, где он был раньше. Это нужно, если вы не хотите настоящего беспорядка. Все, что делает alloca, это перемещает указатель стека, поэтому функция return отменяет это распределение.
Макросы будут работать, потому что они просто добавляют код в одну и ту же функцию. Но это было бы ужасно, без реальной надежды на улучшение.

0 голосов
/ 27 декабря 2011

Стек на самом деле не подходит для того типа распределений, который использует класс контейнера.Например, когда вектору необходимо расширить capacity, он, скорее всего, выделяет новую область хранения, копирует существующие элементы (отсюда требования C ++ к конструкторам копирования и по умолчанию для объектов, используемых в контейнерах) и освобождает исходный объект.место хранения.Излишне говорить, что это разрушает хранилище на основе стека, которое не может быть освобождено до завершения работы функции.(Единственный способ vector развернуть capacity на месте без копирования - это использовать функцию realloc, которая не имеет эквивалента в C ++ и, что наиболее важно для вас, не эквивалентна alloca.)

Кроме того, alloca действительно работает только с POD-типами в C ++, а контейнеры, безусловно, не являются.

EDIT: Ответ на этот вопрос частично решаетпроблема: он выделяет начальное хранилище для vector из стека, но если требуется дополнительная емкость, он выделяется из кучи.

...