Почему использование alloca () не считается хорошей практикой?

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

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

369
задан Matteo Ugolotti 10 January 2019 в 13:45
поделиться

25 ответов

Ответ находится прямо на странице man (по крайней мере, в Linux ):

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ Функция alloca () возвращает указатель на начало выделенное пространство. Если распределение вызывает переполнение стека, поведение программы не определено.

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

218
ответ дан Cristian Ciupitu 10 January 2019 в 13:45
поделиться

ИМХО, alloca считается плохой практикой, потому что все боятся исчерпать ограничение размера стека.

Я многому научился, читая эту ветку и некоторые другие ссылки:

Я использую alloca в основном для мои простые C-файлы, скомпилированные в msvc и gcc без каких-либо изменений, стиль C89, отсутствие #ifdef _MSC_VER и т. д.

Спасибо! Эта тема заставила меня зарегистрироваться на этом сайте:)

1
ответ дан Community 10 January 2019 в 13:45
поделиться

Функция alloca великолепна, и все скептики просто распространяют FUD.

void foo()
{
    int x = 50000; 
    char array[x];
    char *parray = (char *)alloca(x);
}

Массив и parray точно такие же, с точно такими же рисками. Сказать, что один лучше другого - это синтаксический, а не технический выбор.

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

Почему это плохо?

2
ответ дан mlwmohawk 10 January 2019 в 13:45
поделиться

Я не думаю, что кто-то упоминал об этом, но у alloca также есть некоторые серьезные проблемы с безопасностью, которые не обязательно присутствуют в malloc (хотя эти проблемы также возникают с любыми массивами на основе стека, динамическими или нет). Так как память выделяется в стеке, переполнение / переполнение буфера имеет гораздо более серьезные последствия, чем просто malloc.

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

0
ответ дан Dyllon Gagnier 10 January 2019 в 13:45
поделиться

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

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

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

    (...)
}
3
ответ дан Sylvain Rodrigue 10 January 2019 в 13:45
поделиться

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

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

3
ответ дан RichieHindle 10 January 2019 в 13:45
поделиться

Большинство ответов здесь в значительной степени упускают суть: есть причина, почему использование _alloca() потенциально хуже, чем простое хранение больших объектов в стеке.

Основное различие между автоматическим хранением и _alloca() заключается в том, что последний страдает от дополнительной (серьезной) проблемы: выделенный блок не контролируется компилятором , поэтому компилятор не может оптимизировать или переработать его.

Сравните:

while (condition) {
    char buffer[0x100]; // Chill.
    /* ... */
}

с:

while (condition) {
    char* buffer = _alloca(0x100); // Bad!
    /* ... */
}

Проблема с последним должна быть очевидна.

0
ответ дан alecov 10 January 2019 в 13:45
поделиться

Как отмечалось в в этой публикации группы новостей , существует несколько причин, по которым использование alloca можно считать трудным и опасным:

  • Не все компиляторы поддерживают alloca.
  • Некоторые компиляторы по-разному интерпретируют предполагаемое поведение alloca, поэтому переносимость не гарантируется даже между компиляторами, которые его поддерживают.
  • Некоторые реализации содержат ошибки.
39
ответ дан A. Wilcox 10 January 2019 в 13:45
поделиться

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

3
ответ дан user7491277 10 January 2019 в 13:45
поделиться

Старый вопрос, но никто не упомянул, что его следует заменить массивами переменной длины.

char arr[size];

вместо

char *arr=alloca(size);

Он находится в стандарте C99 и существовал как расширение компилятора во многих компиляторах.

70
ответ дан Patrick Schlüter 10 January 2019 в 13:45
поделиться

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

Другими словами, alloca( 0x00ffffff ) опасно и может вызвать переполнение, точно так же, как char hugeArray[ 0x00ffffff ];. Будьте осторожны и разумны, и с вами все будет в порядке.

13
ответ дан JSBձոգչ 10 January 2019 в 13:45
поделиться

alloca () очень полезна, если вы не можете использовать стандартную локальную переменную, потому что ее размер должен быть определен во время выполнения, и вы можете абсолютно гарантировать, что указатель, полученный из alloca (), НИКОГДА не будет использоваться после этой функции возвращается .

Вы можете быть в полной безопасности, если вы

  • не вернете указатель или что-нибудь, что его содержит.
  • не храните указатель в какой-либо структуре, размещенной в куче
  • , не позволяйте никаким другим потокам использовать указатель

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

57
ответ дан Jonathan Leffler 10 January 2019 в 13:45
поделиться

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

25
ответ дан David Thornley 10 January 2019 в 13:45
поделиться

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

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

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

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

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

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

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

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

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

3.2.5.2 Преимущества alloca

4
ответ дан Jonathan Leffler 10 January 2019 в 13:45
поделиться

Все уже указали на большую вещь, которая заключается в потенциальном неопределенном поведении из-за переполнения стека, но я должен отметить, что в среде Windows есть отличный механизм, чтобы уловить это, используя структурированные исключения (SEH) и защитные страницы. Поскольку стек увеличивается только по мере необходимости, эти защитные страницы находятся в незанятых областях. Если вы выделяете их (переполняя стек), возникает исключение.

Вы можете поймать это исключение SEH и вызвать _resetstkoflw, чтобы сбросить стек и продолжить свой веселый путь. Это не идеально, но это еще один механизм, по крайней мере, знать, что что-то пошло не так, когда вещи попадают в фанат. * У nix может быть что-то похожее, чего я не знаю.

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

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

11
ответ дан SilentDirge 10 January 2019 в 13:45
поделиться

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

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

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

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

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

10
ответ дан kriss 10 January 2019 в 13:45
поделиться

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

7
ответ дан Nubok 10 January 2019 в 13:45
поделиться

Одна ловушка с alloca заключается в том, что longjmp перематывает его.

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

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

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

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

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

5
ответ дан Kaz 10 January 2019 в 13:45
поделиться

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

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

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

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

5
ответ дан greggo 10 January 2019 в 13:45
поделиться

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

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

Когда это возможно, VLA могут иметь некоторые преимущества по сравнению с alloca (): компилятор может генерировать проверки ограничения стека, которые будут перехватывать доступ за пределами границ при использовании доступа в стиле массива (я не знаю, делают ли это какие-либо компиляторы, но это может быть сделано), и анализ кода может определить, правильно ли ограничены выражения доступа к массиву. Обратите внимание, что в некоторых средах программирования, таких как автомобильное, медицинское оборудование и авионика, этот анализ должен выполняться даже для массивов фиксированного размера, как автоматического (в стеке), так и статического распределения (глобального или локального).

На архитектурах, которые хранят как данные, так и адреса возврата / указатели фреймов в стеке (насколько я знаю, это все из них), любая переменная, выделенная в стеке, может быть опасной, поскольку адрес переменной может быть получен, а вход не проверен. значения могут допускать всевозможные неприятности.

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

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

1
ответ дан Daniel Glasser 10 January 2019 в 13:45
поделиться

По-прежнему не рекомендуется использовать alloca, почему?

Я не вижу такого консенсуса. Много сильных плюсов; несколько минусов:

  • C99 предоставляет массивы переменной длины, которые часто используются преимущественно как обозначения, более совместимые с массивами фиксированной длины и интуитивно понятными в целом
  • многие системы имеют меньше общей памяти / адресного пространства, доступного для стека, чем для кучи, что делает программу несколько более восприимчивой к исчерпанию памяти (через переполнение стека): это может рассматриваться как хорошая или плохая вещь - один из-за того, что стек автоматически не увеличивается, как это делает куча, состоит в том, чтобы предотвратить неконтролируемые программы, оказывающие столь же неблагоприятное влияние на всю машину
  • при использовании в более локальной области действия (такой как while или for) или в нескольких областях объем памяти накапливается за одну итерацию / область действия и не освобождается до выхода из функции: это контрастирует с нормальными переменными, определенными в области действия структуры управления (например, for {int i = 0; i < 2; ++i) { X } будет накапливаться alloca память запрашивается в X, но память для массива фиксированного размера будет перерабатывается за итерацию).
  • современные компиляторы, как правило, не inline выполняют функции, которые вызывают alloca, но если вы их принудительно заставите, то alloca произойдет в контексте вызывающей стороны (т. Е. Стек не будет освобожден до тех пор, пока вызывающая сторона не вернется)
  • давно alloca перешли от непереносимой функции / хака к стандартизированному расширению, но некоторое негативное восприятие может сохраняться
  • время жизни связано с областью действия функции, которая может или может не подойти программисту лучше, чем явный элемент управления malloc
  • необходимость использовать malloc побуждает задуматься о освобождении - если это управляется через функцию-обертку (например, WonderfulObject_DestructorFree(ptr)), то функция обеспечивает точка для выполнения операций очистки (таких как закрытие файловых дескрипторов, освобождение внутренних указателей или ведение некоторого ведения журнала) без явных изменений в клиентском коде: иногда это хорошая модель для последовательного принятия
    • в этом псевдо-ОО-стиле программирования естественно хотеть что-то вроде WonderfulObject* p = WonderfulObject_AllocConstructor(); - это возможно, когда onstructor "- это функция, возвращающая память malloc (так как память остается выделенной после того, как функция возвращает значение, которое будет сохранено в p), но не в том случае, если« конструктор »использует alloca
      • Макро-версия WonderfulObject_AllocConstructor могла бы достичь этого, но «макросы - это зло» в том смысле, что они могут конфликтовать друг с другом и не-макрокодом и создавать непреднамеренные замены и вытекающие из этого проблемы, которые трудно диагностировать
      • пропущенные free операции могут быть обнаружены ValGrind, Purify и т. Д., Но пропущенные вызовы «деструктора» не всегда могут быть обнаружены вообще - одно очень незначительное преимущество с точки зрения принудительного применения по назначению; некоторые реализации alloca() (такие как GCC) используют встроенный макрос для alloca(), поэтому подстановка диагностической библиотеки использования памяти во время выполнения невозможна, как для malloc / realloc / free ( например, электрический забор)
    • у некоторых реализаций есть тонкие проблемы: например, из справочной страницы Linux:

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


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

    #include <alloca.h>
    #include <iostream>
    #include <vector>
    
    struct Base
    {
        virtual ~Base() { }
        virtual int to_int() const = 0;
    };
    
    struct Integer : Base
    {
        Integer(int n) : n_(n) { }
        int to_int() const { return n_; }
        int n_;
    };
    
    struct Double : Base
    {
        Double(double n) : n_(n) { }
        int to_int() const { return -n_; }
        double n_;
    };
    
    inline Base* factory(double d) __attribute__((always_inline));
    
    inline Base* factory(double d)
    {
        if ((double)(int)d != d)
            return new (alloca(sizeof(Double))) Double(d);
        else
            return new (alloca(sizeof(Integer))) Integer(d);
    }
    
    int main()
    {
        std::vector<Base*> numbers;
        numbers.push_back(factory(29.3));
        numbers.push_back(factory(29));
        numbers.push_back(factory(7.1));
        numbers.push_back(factory(2));
        numbers.push_back(factory(231.0));
        for (std::vector<Base*>::const_iterator i = numbers.begin();
             i != numbers.end(); ++i)
        {
            std::cout << *i << ' ' << (*i)->to_int() << '\n';
            (*i)->~Base();   // optionally / else Undefined Behaviour iff the
                             // program depends on side effects of destructor
        }
    }
    
21
ответ дан Tony Delroy 10 January 2019 в 13:45
поделиться

Множество интересных ответов на этот «старый» вопрос, даже некоторые относительно новые ответы, но я не нашел ни одного, который упомянул бы об этом…

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

Я нашел эту цитату в ... Хорошо, я сделал эту цитату. Но на самом деле, подумай об этом ....

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

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

alloca() (или VLA) может быть просто подходящим инструментом для работы.

Я видел время & amp; снова, когда программист делает выделенный стеком буфер, «достаточно большой, чтобы справиться с любым возможным случаем». В глубоко вложенном дереве вызовов повторное использование этого (анти -?) Шаблона приводит к чрезмерному использованию стека. (Представьте себе дерево вызовов глубиной 20 уровней, где на каждом уровне по разным причинам функция слепо перераспределяет буфер в 1024 байта «просто для безопасности», когда обычно она использует только 16 или менее из них, и только в очень в редких случаях может использоваться больше.) Альтернатива - использовать alloca() или VLA и выделять только столько стекового пространства, сколько требуется вашей функции, чтобы избежать ненужной нагрузки на стек. Надеемся, что когда одна функция в дереве вызовов нуждается в распределении, превышающем нормальное, другие в дереве вызова все еще используют свои обычные небольшие выделения, и общее использование стека приложения значительно меньше, чем если бы каждая функция слепо перераспределяла локальный буфер .

Но если вы решите использовать alloca() ...

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

10
ответ дан phonetagger 10 January 2019 в 13:45
поделиться

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

В заголовочном файле:

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

В файле реализации:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}

Итак, что случилось, это была встроенная функция компилятора DoSomething, и все выделения стека происходили внутри Process() функции и, таким образом, взорвать стек. В мою защиту (и я не был тем, кто обнаружил проблему; мне пришлось пойти и поплакаться с одним из старших разработчиков, когда я не мог это исправить), это было не прямо alloca, это было одно из Макросы преобразования строк ATL.

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

192
ответ дан Jonathan Leffler 10 January 2019 в 13:45
поделиться

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

_alloca block on the stack

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

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

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

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

6
ответ дан rustyx 10 January 2019 в 13:45
поделиться

И вот почему:

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

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

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

9
ответ дан R.. 10 January 2019 в 13:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: