Почему использование alloca () не считается хорошей практикой? - PullRequest
364 голосов
/ 19 июня 2009

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

Почему использование alloca() не рекомендуется, несмотря на вышеперечисленные функции?

Ответы [ 25 ]

10 голосов
/ 02 сентября 2014

alloca () - это хорошо и эффективно ... но оно также глубоко сломано.

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

В большинстве случаев вы можете заменить его, используя локальные переменные и размер мажоранты. Если это используется для больших объектов, то поместить их в кучу обычно более безопасная идея.

Если вам действительно нужно это C, вы можете использовать VLA (нет vla в C ++, тоже неплохо). Они намного лучше, чем alloca () в отношении поведения и согласованности области видимости. На мой взгляд, VLA - это своего рода alloca () , сделанное правильно.

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

9 голосов
/ 02 августа 2010

Вот почему:

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;

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

Практически весь код, использующий alloca и / или C99 vlas, имеет серьезные ошибки, которые приведут к сбоям (если вам повезет) или компрометации привилегий (если вам не повезет).

7 голосов
/ 27 ноября 2010

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

6 голосов
/ 17 февраля 2016

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

_alloca block on the stack

Последствия этого двояки:

  1. Программа будет впечатляюще аварийно завершать работу, и невозможно будет определить причину или причину сбоя (стек, скорее всего, развернется на случайный адрес из-за перезаписанного указателя кадра).

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

Напротив, если вы пишете за пределами блока в куче, вы «просто» получаете повреждение в куче. Программа, вероятно, неожиданно завершит работу, но правильно размотает стек, тем самым уменьшая вероятность выполнения вредоносного кода.

5 голосов
/ 04 ноября 2016

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

Например, общая оптимизация компиляторами C состоит в том, чтобы исключить использование указателя кадра внутри функции, вместо этого доступ к кадру осуществляется относительно указателя стека; так что есть еще один регистр для общего пользования. Но если в функции вызывается alloca, разница между sp и fp будет неизвестна для части функции, поэтому такая оптимизация не может быть выполнена.

Учитывая редкость его использования и его теневое состояние в качестве стандартной функции, разработчики компилятора вполне могут отключить любую оптимизацию, что может вызвать проблемы с alloca, если бы потребовалось больше, чем немного усилий, чтобы заставить его работать с alloca.

UPDATE: Так как локальные массивы переменной длины были добавлены в C и C ++, и поскольку они представляют очень похожие проблемы генерации кода для компилятора, как alloca, я вижу, что «редкость использования и теневое состояние» не применимы к базовому механизму; но я все еще подозреваю, что использование alloca или VLA ведет к компрометации процесса генерации кода внутри функции, которая их использует. Буду рад любым отзывам дизайнеров компиляторов.

4 голосов
/ 30 марта 2011

К сожалению, действительно потрясающий alloca() отсутствует в почти удивительном TCC. Gcc имеет alloca().

  1. Он сеет семя собственного уничтожения. С возвращением в качестве деструктора.

  2. Как и malloc(), он возвращает недопустимый указатель при сбое, который вызовет сбой в современных системах с MMU (и, надеюсь, перезапустит те, у которых нет).

  3. В отличие от автоматических переменных, вы можете указать размер во время выполнения.

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

Если вы нажмете слишком глубоко, вы уверены в сегментации (если у вас MMU).

Обратите внимание, что malloc() не предлагает больше, так как возвращает NULL (что также приведет к segfault, если назначено), когда системе не хватает памяти. То есть все, что вы можете сделать, это внести залог или просто попытаться назначить его любым способом.

Для использования malloc() Я использую глобалы и присваиваю им NULL. Если указатель не равен NULL, я освобождаю его перед использованием malloc().

Вы также можете использовать realloc() в качестве общего случая, если хотите скопировать любые существующие данные. Вы должны проверить указатель, прежде чем выяснить, собираетесь ли вы копировать или объединять после realloc().

3.2.5.2 Преимущества ассигнований

4 голосов
/ 26 февраля 2018

Одна ошибка с alloca состоит в том, что longjmp перематывает его.

То есть, если вы сохраните контекст с помощью setjmp, затем alloca некоторой памяти, а затем longjmp в контексте, вы можете потерять alloca память (без какого-либо уведомления). Указатель стека возвращается туда, где он был, и поэтому память больше не резервируется; если вы вызываете функцию или делаете другую alloca, вы закроете исходную alloca.

Чтобы уточнить, что я здесь конкретно имею в виду, это ситуация, при которой longjmp не возвращается из функции, где имел место alloca! Скорее функция сохраняет контекст с setjmp; затем выделяет память с помощью alloca и, наконец, в этом контексте выполняется longjmp. Память alloca этой функции не полностью освобождена; просто вся память, которую он выделил, начиная с setjmp. Конечно, я говорю о наблюдаемом поведении; ни одно из alloca, которые я знаю, не задокументировано

Обычно в документации основное внимание уделяется концепции, согласно которой alloca память связана с активацией функции , а не с каким-либо блоком; что множественные вызовы alloca просто захватывают больше стековой памяти, которая освобождается после завершения функции. Не так; память фактически связана с контекстом процедуры. Когда контекст восстанавливается с помощью longjmp, то же происходит и с предыдущим alloca состоянием. Это является следствием того, что сам регистр указателя стека используется для распределения, а также (обязательно) сохраняется и восстанавливается в jmp_buf.

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

Я столкнулся с этим как основной причиной ошибки.

3 голосов
/ 30 января 2017

На самом деле, alloca не гарантирует использование стека. Действительно, реализация alloca в gcc-2.95 выделяет память из кучи, используя сам malloc. Кроме того, эта реализация содержит ошибки, это может привести к утечке памяти и неожиданному поведению, если вы вызовете ее внутри блока с дальнейшим использованием goto. Нет, чтобы сказать, что вы никогда не должны его использовать, но иногда alloca приводит к большим накладным расходам, чем освобождает от фрома.

3 голосов
/ 19 июня 2009

Не очень красиво, но если производительность действительно имеет значение, вы можете заранее выделить место в стеке.

Если у вас уже есть максимальный размер блока памяти, который вам нужен, и вы хотите сохранить проверки переполнения, вы можете сделать что-то вроде:

void f()
{
    char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
    SomeType *p = (SomeType *)array;

    (...)
}
3 голосов
/ 19 июня 2009

Процессам доступно только ограниченное количество стекового пространства - намного меньше, чем объем памяти, доступный malloc().

Используя alloca(), вы резко увеличиваете свои шансы на получение ошибки переполнения стека (если вам повезет, или необъяснимого сбоя, если нет).

...