Можно ли перераспределить память alloca ()? - PullRequest
6 голосов
/ 31 мая 2019

Память, выделенная malloc, может быть перераспределена с помощью realloc.Есть ли аналогичная функция для alloca?Перераспределение стековой памяти может быть полезно, когда вы не хотите, чтобы память выделялась в куче, и вам нужно выделять переменную стековую память несколько раз, например, в функции библиотеки, где вам нужна динамическая память, но вы не хотитевыделить в куче, потому что пользователь библиотеки может использовать собственную стратегию выделения кучи.Это выглядело бы так:

int main(void) {
    float * some_mem = alloca(40 * sizeof(float));
    // do something with this memory...

    // now we need a different amount of memory, but some_mem still occupies a lot of the stack, so just reallocate it.

    // is something like this possible?
    some_mem = realloca(some_mem, 50 * sizeof(float));
}

Важно то, что все это происходит в стеке. В: есть ли способ перераспределить память динамического стека?

Ответы [ 2 ]

9 голосов
/ 31 мая 2019

Нет: это не будет работать со стеком, как это обычно делается. Переменная в стеке занимает фиксированный диапазон адресов. Следующая переменная появляется сразу после нее, поэтому нет места для роста. Рассмотрим функцию, подобную этой:

void f(int x) {
    int i;
    float *a = alloca(40 * sizeof(float));
    int k;
    …
}

Стек после пролога функции выглядит примерно так:

----------------+-----+-----+-----+-------------------+-----+---------------------
...             | ret | x   | i   | a                 | k   | ...                 
----------------+-----+-----+-----+-------------------+-----+---------------------
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
previous frames                    f's frame                 free space at the top

Нет места для роста a.

Я показываю очень упрощенный пример: в реальном мире переменные попадают в регистры, переменные можно переупорядочивать, даже если они попадают в стек и т. Д. Но только одна переменная может быть последней в стек с местом для роста.

Таким образом, если существует realloca, его можно применить только к переменной, которая находится наверху стека. (В противном случае ему пришлось бы переместить все остальное, что находится поверх него, но для этого потребовалось бы обновить все существующие указатели на те, что вообще невозможно.) Это был бы очень ограниченный механизм, поэтому поддержка этой функции будет иметь очень маленькая выгода. Его поддержка будет иметь значительную стоимость, поскольку компиляторы обычно могут свободно размещать вещи в стеке в нужном им порядке: для этой функции потребуется новый механизм, позволяющий компилятору знать, что одна конкретная переменная должна идти наверх.

Возможно, что где-то реализация C где-то имеет realloca, но это маловероятно, учитывая соотношение затрат и выгод.

Конечно, realloca может быть легко реализовано, если alloca не использует стратегию распределения стека. Но выделение в стеке - это весь смысл alloca. Если вам нужны объекты с изменяемым размером, вам нужна структура управления памятью с интерфейсом кучи, и именно для этого malloc.


На практике есть несколько возможных подходов к динамическому управлению памятью в библиотеке.

Наиболее распространенный подход - звонить malloc, realloc и free, когда они вам нужны. Вот для чего они.

В некоторых средах полезно поддерживать пользовательские распределители. Вы можете предоставить пользователю библиотеки возможность передавать указатели на альтернативные реализации malloc, realloc и free. Это полезно, когда вы хотите написать переносимую библиотеку, которая должна использоваться кодом, который сам по себе полностью переносим. Однако в большинстве случаев пользователи, которые хотят использовать собственные распределители, могут сделать это, связав своих malloc и друзей. И даже , что редко бывает полезным.

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

/** [documentation of the function] …
 * working_buffer must point to an array of floats of 3*n elements.
 */
void f(size_t n, float *working_buffer);

Лучше, если у вас есть бюджет размера кода, передайте размер массива и проверьте его.

/** [documentation of the function] …
 * working_buffer must point to an array of floats of 3*n elements.  
 */
int f(size_t n, float *working_buffer, size_t working_buffer_length)
{
    if (working_buffer_length < 3 * n) return -EINVAL;
    …
}
3 голосов
/ 31 мая 2019

В принятом ответе правильно указано, что обычно выгоды от realloca недостаточно, потому что ассигнования трудно "увеличить".

Другая проблема, которую я вижу, состоит в том, что у этих распределений есть время жизни до конца функции. Что происходит, когда вы передаете этот указатель другой функции и вызываете там realloca? Эта функция не сможет изменить кадр стека функции глубже в стеке. Он также не может перераспределить его в своем собственном фрейме, потому что объект будет уничтожен, когда он вернется, тогда как исходный объект все еще должен быть живым.

Эта проблема не существует для malloc/realloc, поскольку куча имеет глобальное время жизни.

Можно утверждать, что семантика может быть определена таким образом, что функция может быть перераспределена только в той функции, в которой она была alloc. Это значительно сокращает использование такой функции.

...