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

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

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


person Vaibhav    schedule 19.06.2009    source источник
comment
Просто небольшое примечание. Хотя эту функцию можно найти в большинстве компиляторов, она не требуется стандартом ANSI-C и, следовательно, может ограничивать переносимость. Другое дело, что нельзя! free () указатель, который вы получаете, и он автоматически освобождается после выхода из функции.   -  person merkuro    schedule 19.06.2009
comment
@meruko Хороший момент .. определенно влияет на мобильность   -  person Vaibhav    schedule 19.06.2009
comment
Кроме того, функция с alloca () не будет встроена, если объявлена ​​как таковая.   -  person Justicle    schedule 23.06.2009
comment
@Justicle, можешь дать какое-нибудь объяснение? Мне очень любопытно, что стоит за этим поведением   -  person migajek    schedule 02.08.2010
comment
@migajek, ответ Игоря, только что добавленный ниже, показывает, почему встраивание опасно, и поэтому, если Justicle верен, это должно быть хорошо.   -  person Bill Forster    schedule 05.08.2010
comment
Забудьте весь шум о переносимости, отсутствие необходимости вызывать free (что, очевидно, является преимуществом), невозможность встроить его (очевидно, что распределение кучи намного тяжелее) и т. Д. Единственная причина избегать alloca - это большие размеры. То есть тратить тонны стековой памяти - не лучшая идея, к тому же у вас есть шанс переполнения стека. В этом случае - рассмотрите возможность использования malloca / freea   -  person valdo    schedule 02.11.2011
comment
Есть ли примеры, когда alloca () становится чрезвычайно полезной, например: ее можно использовать для рандомизации распределения стека против атак переполнения стека? Или их нет?   -  person user31986    schedule 07.07.2016
comment
@ user31986 - если в main () вы сделали что-то вроде global_ptr_var=alloca( rand( get_seed()) % 12345) перед вызовом actual_main, это приведет к тому, что вся программа будет работать с (несколько) случайной базой стека. Так что это может быть полезно.   -  person greggo    schedule 02.05.2017
comment
Я бы просто использовал VLA для этого на платформах, которые его поддерживают (возможно, не все компиляторы C поддерживают новейший стандарт, теперь они являются необязательными, а не обязательными, как в C99 ...)   -  person JAB    schedule 22.02.2018
comment
Еще один положительный аспект alloca заключается в том, что стек нельзя фрагментировать, как кучу. Это может оказаться полезным для приложений, работающих в жестком режиме реального времени, или даже для приложений, критически важных для безопасности, поскольку WCRU может быть затем проанализирован статически, не прибегая к настраиваемым пулам памяти с их собственным набором проблем (отсутствие временной локальности, неоптимальный ресурс использовать).   -  person Andreas    schedule 17.05.2018


Ответы (22)


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

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

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

person Sean Bright    schedule 19.06.2009
comment
Так что с этим действительно нет проблем, которых не было бы и с объявлением больших массивов? - person T.E.D.; 19.06.2009
comment
@sean Я понимаю это, если я выделю слишком много памяти (поскольку пространство стека ограничено по сравнению с кучей), это приведет к переполнению; но то же самое и с локальным массивом. Если я останусь в своих пределах, то со мной все будет в порядке, верно? Кроме того, я все еще вижу комментарии против аллоа. - person Vaibhav; 19.06.2009
comment
Да, если вы остаетесь в рамках своих ограничений, ваш код может предполагать определенный размер стека, а стек над фреймом вашей функции всегда имеет тот же размер, все должно быть в порядке. - person Sean Bright; 19.06.2009
comment
Не то же самое, что и большие массивы, поскольку ОС будет гарантировать пространство для них ... они имеют статический размер. - person singpolyma; 23.06.2009
comment
@singpolyma - У меня следующие сбои, поэтому я не совсем понимаю, что вы имеете в виду: int main (int argc, char * argv) {char byebye [1024 * 1024 * 10]; / Массив 10 МБ * /; пока [0] = 0; возврат 0; } - person Sean Bright; 23.06.2009
comment
@Sean: Да, причина - это риск переполнения стека, но это немного глупо. Во-первых, потому что (как говорит Вайбхав) большие локальные массивы вызывают точно такую ​​же проблему, но не так очерняют. Кроме того, рекурсия может так же легко взорвать стек. Извините, но я надеюсь, что вы опровергнете преобладающую идею о том, что причина, указанная на странице руководства, оправдана. - person j_random_hacker; 27.06.2010
comment
Я не совсем понимаю, о чем вы говорите ... Не использовать большие локальные массивы? Функции, которые не работают с неопределенным поведением, следует использовать часто и серьезно? Документации нельзя доверять? - person Sean Bright; 29.06.2010
comment
Я хочу сказать, что обоснование, приведенное на странице руководства, не имеет смысла, поскольку alloca () так же плоха, как и другие вещи (локальные массивы или рекурсивные функции), которые считаются кошерными. - person j_random_hacker; 30.06.2010
comment
Я думаю, что предупреждение на странице руководства имеет смысл. Нет никаких причин, по которым alloca не может применить какое-то волшебство для изменения размера стека для размещения выделения, поэтому стоит отметить, что любое такое волшебство зависит от реализации и не требуется стандартом. - person Charles E. Grant; 02.08.2010
comment
@j_random_hacker: вы действительно думаете, что большие локальные массивы считаются кошерными? - person ninjalj; 02.08.2010
comment
@ninjalj: Не очень опытные программисты C / C ++, но я думаю, что многие люди, которые боятся alloca(), не испытывают такого же страха перед локальными массивами или рекурсией (на самом деле многие люди, которые будут кричать alloca(), будут хвалить рекурсию, потому что она выглядит элегантно) . Я согласен с советом Шона (alloca () подходит для небольших распределений), но я не согласен с мнением о том, что alloca () представляет собой однозначно зло среди трех - они одинаково опасны! - person j_random_hacker; 02.08.2010
comment
alloca можно использовать для небольших выделений, или если ваша функция является листовой функцией (или почти листовой функцией). Таким образом, вы можете установить верхнюю границу того, сколько места в стеке будет использоваться с этого момента. Пример: если у вас есть функция регистрации, которой необходимо преобразовать строку из ASCII в Unicode (или наоборот), может быть более эффективным использовать alloca вместо malloc для буфера преобразования. - person Adam Rosenfield; 11.03.2011
comment
+1, чтобы опровергнуть голосование @ j_random_hacker "против". На странице руководства вполне оправдано заявление о том, что произойдет, если alloca() взорвет стек (на самом деле, было бы безответственно не объяснять это). И окружающее объяснение Шона совершенно здраво, вплоть до уточнения ответа, так что оно не интерпретируется как прямое запрещение alloca(). Ничто в ответе Шона не подразумевает образ мышления, о котором говорит j_random_hacker. Фактически, первые два комментария Шона, опубликованные на несколько часов раньше, чем первый комментарий j_random_hacker, совершенно ясно дают понять, что это не точка зрения Шона. - person Marcelo Cantos; 16.04.2012
comment
Примечание: учитывая оптимистичную стратегию распределения памяти Linux, вы, скорее всего, не получите никаких указаний на сбой исчерпания кучи ... вместо этого malloc () вернет вам хороший указатель, отличный от NULL, а затем когда вы пытаетесь получить доступ к адресному пространству, на которое он указывает, ваш процесс (или какой-либо другой процесс, что непредсказуемо) будет убит OOM-убийцей. Конечно, это особенность Linux, а не проблема C / C ++ как таковая, но об этом следует помнить при обсуждении того, безопаснее ли alloca () или malloc (). :) - person Jeremy Friesner; 28.07.2013
comment
@j_random_hacker: Это действительно очень просто. Вы можете иметь выделения фиксированного размера в стеке без alloca или выделения динамического размера с alloca. В каком-то смысле вы правы - в любом случае нужно внимательно относиться к стеку. - person John Thoits; 17.01.2019
comment
Я бы добавил, что очень жаль, что в этом ответе не указано самое большое преимущество alloca, когда это не неправильный выбор: избегание накладных расходов на удаление / освобождение. Я однажды сделал функцию более чем в 100 раз быстрее, используя alloca просто потому, что убрал накладные расходы на управление памятью кучи. - person John Thoits; 17.01.2019
comment
Кроме того, рекурсия может так же легко взорвать стек. многие современные системы имеют страницы защиты стека, которые используются либо для автоматического увеличения стека, либо, по крайней мере, для создания ошибки. Рекурсия обычно приводит к срабатыванию защитной страницы, в то время как одно большое выделение может прыгать прямо через нее. - person plugwash; 30.10.2020

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

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

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

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

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

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

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

person Igor Zevaka    schedule 04.08.2010
comment
Интересный. Но разве это не считается ошибкой компилятора? В конце концов, встраивание изменило поведение кода (оно задержало освобождение пространства, выделенного с помощью alloca). - person sleske; 17.10.2010
comment
По-видимому, по крайней мере, GCC примет это во внимание: обратите внимание, что некоторые случаи использования в определении функции могут сделать его непригодным для встроенной подстановки. Среди этих вариантов использования: использование varargs, использование alloca, [...]. gcc.gnu.org/onlinedocs/gcc/Inline.html - person sleske; 17.10.2010
comment
Какой компилятор вы курили? - person Thomas Eding; 17.11.2011
comment
Извините, я компилировал в режиме отладки! VS2010 дает: предупреждение C4750: 'bool __cdecl inlined_TestW32Mem5 (void)': функция с _alloca (), встроенная в цикл - person Benj; 15.03.2012
comment
И это если вы _forceinline (в противном случае функция не встроена, если у нее есть alloca) - person Benj; 15.03.2012
comment
Я не понимаю, почему компилятор плохо использует область видимости, чтобы определить, что аллоки в подобласти более или менее освобождены: указатель стека может вернуться в свою точку до входа в область, например, что делается при возврате из функция (не так ли?) - person moala; 16.05.2012
comment
Этот урок не означает, что alloca() не следует использовать. Это просто означает, что вы не должны позволять компилятору встраивать его. Этого легко избежать в GCC и Clang, сказав __attribute__((__noinline__)) для любой функции, вызывающей alloca(). Проблема решена. - person Todd Lehman; 26.08.2015
comment
@moala - это может быть сложным, например если вызывающий абонент также использует alloca. Это, вероятно, не сложнее, чем то, что уже делают компиляторы, но поскольку «alloca» по-прежнему считается «краевой» проблемой, разработчики компилятора вряд ли добавят эту дополнительную сложность для поддержки этого. Связанный момент: наличие alloca в вашей функции заставит оптимизатор `` отбросить '' много вещей, с одной стороны, он больше не знает размер локального кадра и должен использовать регистр кадра, когда в противном случае ему может не понадобиться . - person greggo; 02.05.2017
comment
Я проголосовал против, но ответ хорошо написан: я согласен с другими, вы обвиняете alloca в том, что явно является ошибкой компилятора. Компилятор сделал ошибочное предположение при оптимизации, которую он не должен был делать. Обойти ошибку компилятора можно, но я бы не стал винить за это ничего, кроме компилятора. - person Evan Carroll; 06.09.2018
comment
@EvanCarroll Это не обязательно ошибка компилятора, потому что alloca не является стандартизованной функцией. Чтобы решить проблему с этим компилятором, все, что нужно сделать поставщикам, - это добавить его в документацию. Логика встраивания этой функции компилятора игнорирует alloca. alloca память удаляется только возвратом из не встроенной функции. Это не противоречит ни одному основному стандарту; не ISO C или POSIX, только некоторые де-факто поведения других реализаций. - person Kaz; 09.07.2020
comment
@EvanCarroll Я должен был написать де-факто или задокументировать поведение других реализаций. Кроме того, если мы возьмем историческую перспективу, я считаю, что alloca был изобретен сначала как хакерский метод, представленный в файле заголовка. Осведомленность компилятора о alloca - это более поздняя доработка, но я не думаю, что на нее можно положиться как на универсальную. - person Kaz; 09.07.2020

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

char arr[size];

вместо

char *arr=alloca(size);

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

person Patrick Schlüter    schedule 01.08.2010
comment
Это упомянул Джонатан Леффлер в комментарии к ответу Артура Ульфельдта. - person ninjalj; 02.08.2010
comment
Действительно, но это также показывает, насколько легко это упустить, поскольку я не видел этого, несмотря на то, что прочитал все ответы перед публикацией. - person Patrick Schlüter; 02.08.2010
comment
Одно замечание - это массивы переменной длины, а не динамические массивы. Последние имеют изменяемый размер и обычно размещаются в куче. - person Tim Čas; 09.12.2012
comment
@Benj: Это все еще проблема с последними версиями MSVC? - person einpoklum; 17.07.2015
comment
@einpoklum Я так не верю, хотя и не пробовал: msdn .microsoft.com / en-us / library / hh409293.aspx. - person Benj; 18.07.2015
comment
Visual Studio 2015, компилирующая некоторый C ++, имеет ту же проблему. - person ahcox; 07.04.2016
comment
Как компилятор реализует массив переменного размера? - person Karthik Raj Palanichamy; 14.09.2017
comment
Просто настроив указатель стека. Когда вы вызываете функцию, есть небольшой фрагмент кода, который регулирует sp, а иногда также указатель кадра, который позволяет обращаться к локальным переменным, размещенным в стеке. Указатель стека настраивается в зависимости от размера различных переменных. Для обычных переменных это значение является константой, выдаваемой компилятором. В случае VLA это значение зависит от параметра или другой переменной. Вот почему VLA интересны, их выделение не стоит очень дорого, так как это всего лишь простой расчет, добавленный к операции, которую нужно выполнить в любом случае. - person Patrick Schlüter; 14.09.2017
comment
Линусу Торвальдсу не нравятся VLA в ядре Linux. Начиная с версии 4.20 Linux должен быть почти свободным от VLA. - person Cristian Ciupitu; 10.01.2019
comment
К сожалению, существуют компиляторы (Keil для ARM), которые реализуют VLA в куче, незаметно связывая malloc () с кодом. Это серьезное раздражение, поскольку вы никогда не знаете, где выделяется память, и я не знаю, правильно ли они вызывают free () после этого. - person opt12; 19.09.2019

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

  • Не все компиляторы поддерживают alloca.
  • Некоторые компиляторы по-разному интерпретируют предполагаемое поведение alloca, поэтому переносимость не гарантируется даже между компиляторами, которые его поддерживают.
  • Некоторые реализации содержат ошибки.
person FreeMemory    schedule 19.06.2009
comment
Одна вещь, которую я заметил по этой ссылке, которой нет в другом месте на этой странице, заключается в том, что функция, которая использует alloca(), требует отдельных регистров для хранения указателя стека и указателя кадра. На процессорах x86 ›= 386 указатель стека ESP может использоваться для обоих, освобождая EBP - если не используется alloca(). - person j_random_hacker; 27.06.2010
comment
Еще один хороший момент на этой странице заключается в том, что, если генератор кода компилятора не обработает его как особый случай, f(42, alloca(10), 43); может дать сбой из-за возможности того, что указатель стека будет скорректирован alloca() после, по крайней мере, один из аргументов будет передан. в теме. - person j_random_hacker; 27.06.2010
comment
Связанный пост, кажется, написан Джоном Левином - чуваком, который написал Linkers and Loaders, я бы поверил всему, что он говорит. - person user318904; 13.08.2011
comment
Связанное сообщение является ответом на сообщение Джона Левина. - person A. Wilcox; 29.12.2014
comment
+1 за то, что не приводит нерелевантных аргументов, таких как ответы с гораздо большим количеством голосов, которые забывают, что их аргументы могут быть также применены к массивам переменной длины или любым устройствам, поглощающим стек. - person einpoklum; 18.07.2015
comment
Имейте в виду, что много изменилось с 1991 года. Все современные компиляторы C (даже в 2009 году) должны обрабатывать alloca как особый случай; это внутренняя, а не обычная функция, и она может даже не вызывать функцию. Итак, проблема распределения параметров (которая возникла в K&R C с 1970-х годов) не должна быть проблемой сейчас. Более подробно в комментарии, который я сделал к ответу Тони Д. - person greggo; 02.05.2017
comment
Вопрос: есть ли эквивалент alloca, поддерживаемый всеми компиляторами? - person mercury0114; 04.10.2020

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

person David Thornley    schedule 19.06.2009

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

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

  • C99 предоставляет массивы переменной длины, которые часто предпочтительнее использовать, поскольку нотация более согласована с массивами фиксированной длины и интуитивно понятна в целом.
  • многие системы имеют меньше общей памяти / адресного пространства, доступного для стека, чем для кучи, что делает программу немного более восприимчивой к исчерпанию памяти (из-за переполнения стека): это можно рассматривать как хорошо или плохо - одно из причин, по которым стек не увеличивается автоматически, как это делает куча, является предотвращение того, чтобы неконтролируемые программы оказывали столь же негативное влияние на всю машину
  • при использовании в более локальной области (например, в цикле while или for) или в нескольких областях память накапливается за итерацию / область действия и не освобождается до выхода из функции: это контрастирует с обычными переменными, определенными в области действия управляющей структуры. (например, for {int i = 0; i < 2; ++i) { X } будет накапливать alloca-ed память, запрошенную в X, но память для массива фиксированного размера будет повторно использоваться за итерацию).
  • современные компиляторы обычно не inline функции, которые вызывают alloca, но если вы заставите их, то alloca произойдет в контексте вызывающих (т.е. стек не будет освобожден до тех пор, пока вызывающий не вернется)
  • давным-давно alloca перешел от непереносимой функции / хака к стандартизированному расширению, но некоторое негативное восприятие может сохраниться
  • время жизни привязано к области действия функции, которая может или не может устраивать программиста лучше, чем явное управление malloc
  • having to use malloc encourages thinking about the deallocation - if that's managed through a wrapper function (e.g. WonderfulObject_DestructorFree(ptr)), then the function provides a point for implementation clean up operations (like closing file descriptors, freeing internal pointers or doing some logging) without explicit changes to client code: sometimes it's a nice model to adopt consistently
    • in this pseudo-OO style of programming, it's natural to want something like WonderfulObject* p = WonderfulObject_AllocConstructor(); - that's possible when the "constructor" is a function returning malloc-ed memory (as the memory remains allocated after the function returns the value to be stored in p), but not if the "constructor" uses alloca
      • a macro version of WonderfulObject_AllocConstructor could achieve this, but "macros are evil" in that they can conflict with each other and non-macro code and create unintended substitutions and consequent difficult-to-diagnose problems
    • отсутствующие free операции могут быть обнаружены ValGrind, Purify и т. д., но отсутствующие вызовы «деструктора» не всегда могут быть обнаружены вообще - одно очень незначительное преимущество с точки зрения обеспечения предполагаемого использования; некоторые реализации alloca() (например, GCC) используют встроенный макрос для alloca(), поэтому замена библиотеки диагностики использования памяти во время выполнения невозможна, как для _20 _ / _ 21 _ / _ 22_ (например, электрический забор)
  • some implementations have subtle issues: for example, from the Linux manpage:

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


  • Я знаю, что этот вопрос помечен тегом C, но как программист на C ++ я подумал, что буду использовать C ++, чтобы проиллюстрировать потенциальную полезность alloca: приведенный ниже код (и здесь at 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
        }
    }
    
    person Tony Delroy    schedule 26.02.2013
    comment
    нет +1 из-за подозрительного идиосинкразического подхода к нескольким типам :-( - person einpoklum; 18.07.2015
    comment
    @einpoklum: ну, это поучительно ... спасибо. - person Tony Delroy; 18.07.2015
    comment
    Перефразирую: это очень хороший ответ. Вплоть до того момента, когда я думаю, вы предлагаете людям использовать своего рода контр-паттерн. - person einpoklum; 18.07.2015
    comment
    Комментарий с man-страницы linux очень старый и, я уверен, устарел. Все современные компиляторы знают, что такое alloca (), и не споткнутся вот так. В старом K&R C: (1) все функции использовали указатели кадров (2) Все вызовы функций были {push args on stack} {call func} {add # n, sp}. alloca была функцией библиотеки, которая просто поднимала стек вверх, компилятор даже не знал об этом. (1) и (2) больше не верны, поэтому alloca не может работать таким образом (теперь это внутренняя функция). В старом C вызов alloca в середине отправки аргументов, очевидно, нарушил бы и эти предположения. - person greggo; 02.05.2017
    comment
    Что касается примера, меня обычно беспокоит что-то, что требует always_inline, чтобы избежать повреждения памяти .... - person greggo; 02.05.2017
    comment
    Использует новое размещение для возврата буфера выделения. Если функция оказывается не встроенной, вы уничтожаете стек. Ваш код не определен. - person Joshua; 25.09.2019
    comment
    если кто-нибудь прочитает это: современный стиль C ++ для размещения в стеке осуществляется через allocator - создайте его и направьте все векторы и новые через него - person Noone AtAll; 22.07.2020

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

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

person JSBձոգչ    schedule 19.06.2009

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

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

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

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

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

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

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

Но если вы решите использовать _9 _...

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

person phonetagger    schedule 31.03.2016
comment
Я согласен с этим. Опасность alloca() верна, но то же самое можно сказать и об утечках памяти с malloc() (почему бы тогда не использовать сборщик мусора? Кто-то может возразить). alloca() при осторожном использовании может быть действительно полезным для уменьшения размера стека. - person Felipe Tonello; 02.02.2017
comment
Еще одна веская причина не использовать динамическую память, особенно встроенную: это сложнее, чем придерживаться стека. Использование динамической памяти требует специальных процедур и структур данных, тогда как в стеке это (для упрощения) вопрос добавления / вычитания большего числа из указателя стека. - person tehftw; 17.09.2018
comment
Замечание: пример использования фиксированного буфера [MAX_SIZE] показывает, почему политика избыточного выделения памяти работает так хорошо. Программы выделяют память, которую они никогда не могут трогать, за исключением пределов длины их буфера. Так что нормально, что Linux (и другие ОС) фактически не назначают страницу памяти до ее первого использования (в отличие от malloc'd). Если размер буфера превышает одну страницу, программа может использовать только первую страницу и не тратить впустую остальную физическую память. - person Katastic Voyage; 23.06.2019
comment
@KatasticVoyage Если MAX_SIZE не больше (или, по крайней мере, равно) размеру страницы виртуальной памяти вашей системы, ваш аргумент не выдерживает критики. Также во встроенных системах без виртуальной памяти (многие встроенные микроконтроллеры не имеют MMU) политика избыточной памяти может быть хорошей с точки зрения гарантии того, что ваша программа будет работать во всех ситуациях, но эта гарантия идет с той ценой, что размер вашего стека должен быть аналогичным образом выделяется для поддержки этой политики избыточной памяти. В некоторых встроенных системах это цена, которую некоторые производители недорогой продукции не готовы платить. - person phonetagger; 05.09.2019

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

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

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

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

person SilentDirge    schedule 21.03.2011
comment
Проблема в том, что alloca() может потребовать столько места, что указатель стека оказывается в куче. При этом злоумышленник, который может контролировать размер alloca() и данные, которые попадают в этот буфер, может перезаписать кучу (что очень плохо). - person 12431234123412341234123; 02.11.2018
comment
SEH предназначен только для Windows. Это здорово, если вы заботитесь только о своем коде, работающем в Windows, но если ваш код должен быть кроссплатформенным (или если вы пишете код, который работает только на платформе, отличной от Windows), тогда вы не можете полагаться на наличие SEH. - person George; 11.06.2020

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

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

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

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

person greggo    schedule 04.11.2016
comment
Массивы переменной длины никогда не добавлялись в C ++. - person Nir Friedman; 20.11.2019
comment
@NirFriedman В самом деле. Я думаю, что был список функций Википедии, основанный на старом предложении. - person greggo; 24.11.2019
comment
Я все еще подозреваю, что использование alloca или VLA имеет тенденцию к компрометации генерации кода Я бы подумал, что для использования alloca требуется указатель кадра, потому что указатель стека перемещается способами, которые не очевидны во время компиляции . alloca может быть вызвана в цикле, чтобы захватить больше памяти стека, или с расчетным размером во время выполнения и т. д. Если есть указатель кадра, сгенерированный код имеет стабильную ссылку на локальные переменные, и указатель стека может делать все, что захочет; не используется. - person Kaz; 12.06.2020
comment
Если альтернативой VLA или alloca является вызов malloc и free, функция может быть более эффективной с их использованием, даже если она требует кадрирования. - person Kaz; 09.07.2020

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

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

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

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

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

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

person Kaz    schedule 26.02.2018
comment
Это то, что он должен делать - longjmp возвращается и заставляет программу забыть обо всем, что произошло в стеке: обо всех переменных, вызовах функций и т. Д. И alloca подобен массиву в стеке, поэтому ожидается, что они будут быть затертым, как и все остальное в стеке. - person tehftw; 17.09.2018
comment
man alloca дал следующее предложение: Поскольку пространство, выделенное функцией alloca (), выделяется внутри фрейма стека, это пространство автоматически освобождается, если возврат функции перепрыгивает через вызов longjmp (3) или siglongjmp (3) .. Так что это задокументировано, что память, выделенная с помощью alloca, затирается после longjmp. - person tehftw; 17.09.2018
comment
@tehftw Описанная ситуация возникает без перепрыгивания возврата функции longjmp. Целевая функция еще не вернулась. Это было сделано setjmp, alloca, а затем longjmp. longjmp может перемотать alloca состояние обратно к тому, что было в setjmp время. То есть указатель, перемещаемый alloca, страдает той же проблемой, что и локальная переменная, которая не была отмечена volatile! - person Kaz; 17.09.2018
comment
Я не понимаю, почему это должно быть неожиданным. Когда вы setjmp, затем alloca, а затем longjmp, это нормально, что alloca будет перематываться. Весь смысл longjmp в том, чтобы вернуться к состоянию, которое было сохранено на setjmp! - person tehftw; 17.09.2018
comment
@tehftw Я никогда не видел документального подтверждения этого конкретного взаимодействия. Следовательно, на него нельзя полагаться ни в каком другом случае, кроме как путем эмпирического исследования с компиляторами. - person Kaz; 18.09.2018
comment
man alloca задокументировал это взаимодействие. Я лично полагался бы на это взаимодействие, если бы я использовал alloca с longjmp, как это задокументировано. Какую документацию для alloca вы читали, чтобы ее там не было? - person tehftw; 18.09.2018
comment
Справочная страница @tehftw Linux Programmer's Manual; Документация GCC на __builtin_alloca() (основа для alloca), MSDN на Microsoft alloca; Справочная страница Solaris 11; Справочная страница FreeBSD. Только Linux упоминает взаимодействие между alloca и longjmp; но только об освобождении памяти, когда longjmp перепрыгивает через возврат функции. - person Kaz; 18.09.2018
comment
Забавно, что в MSDN этого нет, так как я хотел спросить, не являются ли Micrisoft теми парнями, которые не смогли это задокументировать: P Я удивлен тем, что другие не говорят об этом прямо. Из того, что я читал, кажется, что GCC, FreeBSD и MSDN предполагают, что среда стека восстановления достаточно ясна, и память из alloca будет забыта после longjmp перед ней. - person tehftw; 19.09.2018
comment
Мне кажется, это доказывает, что _1 _ / _ 2_ опасные звери. - person Yongwei Wu; 22.07.2020

Вот почему:

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

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

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

person R.. GitHub STOP HELPING ICE    schedule 01.08.2010
comment
Возможно, мир никогда не узнает. :( Тем не менее, я надеюсь, что вы могли бы прояснить мой вопрос о alloca. Вы сказали, что почти во всем коде, который его использует, есть ошибка, но я планировал использовать ее; я обычно игнорирую такое утверждение, но исходящий от вас, я не буду. Я пишу виртуальную машину, и я хотел бы выделить переменные, которые не выходят из функции в стеке, а не динамически из-за огромного ускорения. альтернативный подход с такими же характеристиками производительности? Я знаю, что могу приблизиться к пулам памяти, но это все еще не так дешево. Что бы вы сделали? - person GManNickG; 26.05.2011
comment
Не стесняйтесь размещать небольшие объекты в стеке - это безопасно, но в этом случае один буфер фиксированного размера (например, unsigned char buf[1024];) с некоторым простым кодом для управления его разделением и поддержанием выравнивания будет работать так же хорошо и будет более портативным избегая alloca. Единственные преимущества, которые может дать VLA или alloca, - это возможность делать произвольно большие выделения (с сопутствующей опасностью) или возможность делать произвольно маленькие выделения (например, чтобы не тратить впустую пространство стека, когда требуется менее 1024 байтов). - person R.. GitHub STOP HELPING ICE; 26.05.2011
comment
*0=9; недействителен C. Что касается тестирования размера, который вы передаете в alloca, протестируйте его против чего? Невозможно узнать предел, и если вы просто собираетесь протестировать его с крошечным фиксированным известным безопасным размером (например, 8 КБ), вы также можете просто использовать массив фиксированного размера в стеке. - person R.. GitHub STOP HELPING ICE; 17.11.2011
comment
Проблема с тем, что ваш размер либо достаточно мал, либо он зависит от ввода и, следовательно, может быть сколь угодно большим аргументом, поскольку я вижу, что он так же сильно применяется к рекурсии. Практический компромисс (для обоих случаев) - предположить, что если размер ограничен small_constant * log(user_input), то у нас, вероятно, достаточно памяти. - person j_random_hacker; 16.04.2012
comment
Действительно, вы определили ОДИН случай, когда VLA / alloca полезен: рекурсивные алгоритмы, в которых максимальное пространство, необходимое для любого кадра вызова, может быть равно N, но где сумма пространства, необходимого на всех уровнях рекурсии, равна N или некоторой функции из N, который не растет быстро. - person R.. GitHub STOP HELPING ICE; 16.04.2012
comment
Проницательный ответ. Существуют ли исторические уязвимости, которые возникли в результате использования alloca() на входах, контролируемых злоумышленником (или выделения VLA на его основе), возможно, записанных как CVE? - person Pascal Cuoq; 24.03.2014
comment
Невозможно узнать предел - Стек может быть явно установлен [1], поэтому его можно узнать, это просто не очень практично. 1 - pthread_attr_setstack - person bestsss; 11.06.2014
comment
@bestsss: Это не помогает, потому что вы не можете контролировать, сколько компилятор использует. Очевидно, настоящие компиляторы стремятся к некоторому здравомыслию, но они все равно могут тратить много времени на выравнивание кадров стека, встраивание функций с большими буферами на основе стека и не повторное использование пространства, как только встроенная функция будет выполнена, и т. Д. BTW pthread_attr_setstack имеет некоторые серьезные проблемы и не следует использовать. pthread_attr_setstacksize - предпочтительная операция. - person R.. GitHub STOP HELPING ICE; 11.06.2014
comment
Каковы проблемы помимо непереносимости - например, архитектуры с двумя стеками, один для данных, один для адресов возврата и трехкратной зоной (например, Itanium)? Я имею в виду, помимо некоторых трудностей с его правильным использованием, что может вызвать проблемы? - person bestsss; 11.06.2014
comment
почти наверняка исходит от какого-то вклада? Не мой код. Я использую alloca() для размещения структур переменного размера (то есть структур с массивом переменного размера в качестве последнего элемента). Это круто .. - person Todd Lehman; 26.08.2015
comment
@ToddLehman: Откуда взялся размер последнего элемента переменного размера? - person R.. GitHub STOP HELPING ICE; 26.08.2015
comment
@R .. - статическая таблица констант. Или в большинстве случаев (в программе, в которой я его использую чаще всего) это количество множителей в простой факторизации числа, которое по своей сути ограничено 63 для 64-битного целого числа - независимо от того, насколько велико, даже если это предоставляется пользователем. Поэтому я могу смело ставить свою жизнь на то, что элементов никогда не бывает больше 63. Фактически, это выходит за рамки должной осмотрительности при проверке вводимых пользователем данных. Здесь физически невозможно (если не считать ошибки кода) иметь более 63 элементов. - person Todd Lehman; 26.08.2015
comment
@ToddLehman: Тогда нет смысла использовать alloca. Используйте массив из 63 элементов фиксированного размера. Использование стека не будет заметно отличаться, и ваша программа будет работать лучше (потому что alloca требует регистр указателя кадра или эквивалент, увеличивая давление регистров и сложность доступа к локальным данным). Как обсуждалось выше, почти единственные места, где вы не можете просто заменить массив постоянного размера вместо alloca, - это места, где alloca небезопасно. - person R.. GitHub STOP HELPING ICE; 26.08.2015
comment
@R .. - Нет! Не хорошо. Нельзя использовать для этого массив из 63 элементов фиксированного размера, потому что тогда мне пришлось бы иметь два отдельных определения структуры (одно для массива фиксированного размера и одно для массива переменного размера), которые, в свою очередь, означает вдвое больше функций, которые работают со структурой. Мне действительно нужен массив переменного размера для структур, которые выделяются из кучи. В некоторых случаях я могу выделить в стеке временную обработку. Действительно приятно иметь только одну структуру, которая делает все это, и было бы кошмаром, если бы мне пришлось использовать массив фиксированного размера. alloca - чистая сладость для этого. - person Todd Lehman; 26.08.2015
comment
@ToddLehman: У старых компиляторов не было проблем с кодом, который использовал одну функцию для работы со структурами, которые совпадали, за исключением размера включенного массива. Хотелось бы, чтобы Стандарт признал, что такие методы были полезны и должны быть признаны на 99% платформ, где они, естественно, будут работать, если оптимизатор не будет мешать. - person supercat; 01.04.2016
comment
@supercat - Но даже тогда зачем вообще нужны два разных определения структуры (одно для кучи, другое для стека)? alloca безумно быстр. Для всех практических целей разместить структуру переменного размера в стеке с помощью alloca так же быстро, как выделить структуру фиксированного размера в стеке в качестве переменной. Я имею в виду, конечно, было бы сработать два разных определения и, возможно, сделать приведение, чтобы компилятор был доволен, но зачем беспокоиться? Динамическое выделение структуры переменного размера в стеке экономит место по сравнению с выделением фиксированного размера и не тратит время. Ешьте торт, ешьте его тоже! - person Todd Lehman; 01.04.2016
comment
@ToddLehman: способ указания alloca () делает невозможным для многих компиляторов его поддержку без - как минимум - отключения того, что в противном случае было бы полезной оптимизацией. Обычная реализация предполагает, что во время выполнения alloca () в стеке не будет ничего, что нужно было бы удалить, прежде чем в следующий раз указатель стека можно будет перезагрузить с указателем кадра. Если эта перезагрузка не произойдет должным образом (например, из-за встраивания функций), могут произойти неприятности. На самом деле, я думаю, вы могли бы обойтись одной функцией для работы со структурой, если бы вы использовали объединение ... - person supercat; 01.04.2016
comment
... между структурой FAM и массивом фиксированного размера, достаточным для принудительного выделения памяти. Если бы у alloca () было более строго определенное использование, которое требовало использования freea () [, которое можно было бы указать либо как требующее, чтобы каждый alloca () был сопоставлен с freea (), либо говоря, что любой freea () освободит все выделенное после данного объекта], тогда все компиляторы могли бы его реализовать. Концепция выделения LIFO является хорошей, независимо от того, использует ли он стек выполнения для хранения переменных, поскольку он может избежать фрагментации, если код создает временный объект предполагаемого размера ... - person supercat; 01.04.2016
comment
... а затем создает новый объект точного размера, соответствующим образом форматирует данные из временного объекта в новый и больше не нуждается во временном объекте. Даже если реализация использовала кучу для временного объекта, она могла бы сделать выделение несколько завышенным и повторно использовать один и тот же объект между использованиями. - person supercat; 01.04.2016
comment
@supercat - Разве union не приведет к размеру, равному большему из двух размеров внутри него, например, всегда размер структуры фиксированного размера? Это было бы нормально в случае выделения стека, но в случае выделения кучи это нарушило бы всю цель использования структуры переменного размера. Интересная идея, если бы ее можно было заставить работать. Что касается портативности ... хороший момент. Однако в моем случае переносимость - это не то, что меня волнует, потому что я пишу специально для Clang / LLVM и iOS, поэтому я использую alloca широко. Но я понимаю опасения. - person Todd Lehman; 01.04.2016
comment
@ToddLehman: вы должны использовать объединение только для распределения стека и передавать адрес объекта внутри него в код, ожидающий типа кучи. - person supercat; 01.04.2016
comment
@supercat - Интересно ... может сработать! В качестве альтернативы вы можете определить две структуры (одну с массивом с нулевым элементом в качестве последнего элемента, а другую с массивом ненулевой длины в качестве последнего элемента) и привести указатель вместо использования union. Не так чисто, но это позволило бы избежать трех typedef (один для структуры переменного размера, один для структуры фиксированного размера и один для объединения), потому что для этого потребовалось бы только два typedef. С другой стороны, alloca все же, возможно, лучше, чем писать по два typedef для каждой структуры переменной длины, если доступно alloca. - person Todd Lehman; 01.04.2016
comment
Позвольте нам продолжить это обсуждение в чате. - person supercat; 01.04.2016
comment
Ваш аргумент против alloca из-за переменного размера ввода можно использовать и для многих рекурсивных функций. - person Thomas Eding; 10.01.2021
comment
@ Томас: Да и? - person R.. GitHub STOP HELPING ICE; 10.01.2021

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

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

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

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

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

person kriss    schedule 02.09.2014
comment
Я не вижу причин для отрицательного голоса (кстати, без комментариев) - person gd1; 25.02.2015
comment
Только имена имеют область действия. alloca не создает имени, а только диапазон памяти, имеющий время жизни. - person curiousguy; 27.10.2015
comment
@curiousguy: ты просто играешь словами. Для автоматических переменных я мог бы также говорить о времени жизни базовой памяти, поскольку она соответствует области действия имени. В любом случае проблема не в том, как мы это называем, а в нестабильности времени жизни / объема памяти, возвращаемой функцией alloca, и исключительном поведении. - person kriss; 27.10.2015
comment
Я бы хотел, чтобы у alloca был соответствующий freea, со спецификацией, согласно которой вызов freea отменял бы эффекты alloca, создавшего объект, и все последующие, и требование, чтобы хранилище, 'выделенное' внутри функции, должно быть 'освобождено' внутри это тоже. Это позволило бы почти всем реализациям поддерживать alloca / freea совместимым образом, облегчило бы проблемы с встраиванием и в целом сделало бы вещи намного чище. - person supercat; 01.04.2016
comment
@supercat - Я тоже так желаю. По этой причине (и не только) я использую уровень абстракции (в основном макросы и встроенные функции), чтобы никогда не вызывать alloca, malloc или free напрямую. Я говорю такие вещи, как {stack|heap}_alloc_{bytes,items,struct,varstruct} и {stack|heap}_dealloc. Итак, heap_dealloc просто вызывает free, а stack_dealloc не работает. Таким образом, распределение стека может быть легко понижено до распределения кучи, и намерения также более ясны. - person Todd Lehman; 01.04.2016
comment
вызов alloca внутри функции предотвращает ее встраивание. - person greggo; 12.10.2020

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

person Sondhi Chakraborty    schedule 27.11.2010
comment
alloca() не более опасен, чем int foo[bar];, где bar - произвольное целое число. - person Todd Lehman; 26.08.2015
comment
@ToddLehman Это верно, и именно по этой причине мы запретили VLA в ядре в течение нескольких лет, и с 2018 года мы не поддерживаем VLA :-) - person Chris Down; 25.04.2020

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

«Блок

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

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

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

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

person rustyx    schedule 17.02.2016
comment
Ничто в этой ситуации кардинально не отличается от опасностей переполнения буфера фиксированного размера, выделенного для стека. Эта опасность характерна не только для alloca. - person phonetagger; 01.04.2016
comment
Конечно, нет. Но проверьте исходный вопрос. Возникает вопрос: в чем опасность alloca по сравнению с malloc (таким образом, буфер не фиксированного размера в стеке). - person rustyx; 01.04.2016
comment
Незначительный момент, но в некоторых системах стеки растут вверх (например, в 16-битных микропроцессорах PIC). - person EBlake; 05.03.2020
comment
@EBlake Как выглядят их кучи? - person Sapphire_Brick; 07.06.2021

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

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

person RichieHindle    schedule 19.06.2009
comment
Это во многом зависит от приложения. Для встроенного приложения с ограничением памяти нет ничего необычного в том, что размер стека превышает размер кучи (если куча вообще существует). - person EBlake; 05.03.2020

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

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

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

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

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

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

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

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

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

3.2. 5.2 Преимущества alloca

person zagam    schedule 30.03.2011
comment
На самом деле спецификация alloca не говорит, что возвращает недопустимый указатель при ошибке (переполнение стека), она говорит, что имеет неопределенное поведение ... а для malloc она говорит, что возвращает NULL, а не случайный недопустимый указатель (хорошо, оптимистическая реализация памяти Linux делает это бесполезный). - person kriss; 02.09.2014
comment
@kriss Linux может убить ваш процесс, но, по крайней мере, он не решается на неопределенное поведение - person craig65535; 06.10.2017
comment
@ craig65535: выражение undefined behavior обычно означает, что это поведение не определено спецификациями C или C ++. Ни в коем случае не будет случайным или нестабильным на любой данной ОС или компиляторе. Поэтому бессмысленно связывать UB с названием ОС вроде Linux или Windows. Это не имеет к этому никакого отношения. - person kriss; 07.10.2017
comment
Я пытался сказать, что malloc, возвращающий NULL, или, в случае Linux, доступ к памяти, убивающий ваш процесс, предпочтительнее неопределенного поведения alloca. Думаю, я, должно быть, неправильно прочитал ваш первый комментарий. - person craig65535; 07.10.2017

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

person user7491277    schedule 30.01.2017
comment
Похоже, что gcc-2.95 сломал alloca и, вероятно, не может безопасно использоваться для программ, требующих alloca. Как бы он очистил память, если longjmp используется для отказа от кадров, которые сделали alloca? Когда сегодня кто-нибудь будет использовать gcc 2.95? - person Kaz; 18.09.2018

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

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

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

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

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

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

person Daniel Glasser    schedule 13.12.2016

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

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

person Dyllon Gagnier    schedule 30.12.2018

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

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

Сравнивать:

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

с участием:

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

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

person alecov    schedule 28.04.2017
comment
Есть ли у вас практические примеры, демонстрирующие разницу между VLA и alloca (да, я говорю VLA, потому что alloca - это больше, чем просто создатель массивов статического размера)? - person Ruslan; 29.04.2017
comment
Для второго есть варианты использования, которые первый не поддерживает. Я могу захотеть иметь n записей после того, как цикл будет выполнен n раз - возможно, в связанном списке или дереве; эта структура данных затем удаляется, когда функция в конечном итоге возвращается. Это не значит, что я бы так все закодировал :-) - person greggo; 18.01.2018
comment
И я бы сказал, что компилятор не может это контролировать, потому что так определяется alloca (); современные компиляторы знают, что такое alloca, и обращаются с ней особым образом; это не просто библиотечная функция, как в 80-х. C99 VLA в основном распределены с областью видимости блока (и улучшенной типизацией). Никакого большего или меньшего контроля, просто соответствие другой семантике. - person greggo; 18.01.2018
comment
@greggo: Если вы проголосовали против, я бы с радостью услышал, почему вы считаете, что мой ответ бесполезен. - person alecov; 18.01.2018
comment
В C переработка не является задачей компилятора, вместо этого это задача библиотеки c (free ()). alloca () освобождается при возврате. - person peterh; 10.12.2019
comment
Компилятор, безусловно, может оптимизировать и повторно использовать _alloca(FIXED_COMPILE_TIME_VALUE) во многих случаях. - person Thomas Eding; 10.01.2021
comment
Если вы вытащите alloca из цикла, все будет в порядке - person user16217248; 28.06.2021

person    schedule
comment
Функция VLA (массив переменной длины) в C99 поддерживает локальные переменные с динамическим размером без явного требования использования alloca (). - person Jonathan Leffler; 23.06.2009
comment
аккуратно! дополнительную информацию можно найти в разделе «3.4 Массив переменной длины» страницы programmersheaven.com/ 2 / Указатели-и-массивы-страница-2 - person Arthur Ulfeldt; 23.06.2009
comment
Но это не отличается от работы с указателями на локальные переменные. Их тоже можно обмануть ... - person glglgl; 18.10.2012
comment
@Jonathan Leffler. Одна вещь, которую вы можете сделать с alloca, но не можете сделать с VLA, - это использовать с ними ключевое слово restrict. Примерно так: float * restrict heavyily_used_arr = alloca (sizeof (float) * size); вместо float heavyily_used_arr [размер]. Это может помочь некоторым компиляторам (в моем случае gcc 4.8) оптимизировать сборку, даже если размер является константой компиляции. См. Мой вопрос по этому поводу: stackoverflow.com/questions/19026643/using- ограничить-с-массивами - person Piotr Lopusiewicz; 02.10.2013
comment
@JonathanLeffler VLA является локальным по отношению к блоку, который его содержит. С другой стороны, alloca() выделяет память, которой хватит до конца функции. Это означает, что не существует прямого и удобного перевода f() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ } на VLA. Если вы считаете, что можно автоматически переводить использование alloca в использование VLA, но для описания того, как это сделать, требуется нечто большее, чем просто комментарий, я могу задать этот вопрос. - person Pascal Cuoq; 24.03.2014
comment
@PascalCuoq: не всегда существует прямой, не говоря уже о автоматическом переводе между кодом, использующим alloca(), и кодом, использующим VLA. Я заметил, что в вашем f() код /* use p */, вероятно, должен иметь дополнительные условные выражения в зависимости от того, является ли p нулевым или нет. Суть кода, использующего VLA, может быть следующей: f() { int x = 1; if (c) { /* compute x */ } char p[x]; /* use p */ }. - person Jonathan Leffler; 24.03.2014
comment
Некоторые гниды: (1) Безопасно возвращать указатель, если вызывающий объект использует этот указатель только для адресации статистики и никогда не разыменовывает его. (2) Совершенно безопасно хранить указатель в структуре, размещенной в куче, если вы удаляете структуру из кучи до закрытия фрейма стека. Я не знаю, зачем кому-то это делать, но это должно быть совершенно безопасно. (3) Можно позволить другим потокам использовать указатель, если вы можете гарантировать, что память действительна в течение всего времени, в течение которого они ее используют - например, для некоторых длительных вычислений. - person Todd Lehman; 15.05.2015
comment
Также отметим, что вы должны следовать тому же ограничению, которое упоминает Артур в отношении VLA, а также alloca() результатов. - person einpoklum; 17.07.2015
comment
Хороший ответ и хороший пример того, когда без комментариев не обойтись - скажите будущему программисту, что ему лучше знать, что он делает, или пусть это сделает кто-то другой. - person John Thoits; 17.01.2019
comment
Я бы добавил: сначала проверьте размер, если он зависит от ненадежных данных, перед вызовом, чтобы вы не просили абсурдно большого увеличения фрейма стека. И (как и все вышеперечисленное) это также относится к VLA. - person greggo; 20.06.2019