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


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

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

24 330

24 ответа:

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

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

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

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

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

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

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

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

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

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

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

char arr[size];

вместо

char *arr=alloca(size);

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

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

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

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

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

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

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

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

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

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

  • C99 предоставляет массивы переменной длины, которые часто используются предпочтительно в качестве нотации, более совместимой с массивами фиксированной длины и интуитивно понятной в целом
  • многие системы имеют меньше общего объема памяти/адресного пространства, доступного для стека, чем для кучи, что делает программа немного более восприимчива к истощению памяти (через переполнение стека): это может рассматриваться как хорошая или плохая вещь - одна из причин, по которой стек не растет автоматически, как куча,-это предотвращение выхода из-под контроля программ, оказывающих такое же негативное влияние на всю машину
  • при использовании в более локальной области (например,while или for цикл) или в нескольких областях, память накапливается за итерацию / область и не освобождается до выхода функции: это контрастирует с обычными переменными, определенными в области структуры управления (например,for {int i = 0; i < 2; ++i) { X } будет аккумулировать alloca-ed память, запрошенная в X, но память для массива фиксированного размера будет переработана за итерацию).
  • современные компиляторы обычно не inline функции, которые называют alloca, но если вы заставите их потом alloca произойдет в контексте вызывающих абонентов (т. е. стек не будет освобожден до тех пор, пока вызывающий абонент не вернется)
  • давным-давно alloca переход от непортативной функции / взлома к стандартизированному расширению, но некоторое негативное восприятие может сохраняться
  • время жизни привязано к области действия функции, которая может или не может подходить программисту лучше, чем malloc'явного управления s
  • использовать malloc поощряет думать об освобождении-если это управляется через функцию-оболочку (например,WonderfulObject_DestructorFree(ptr)), то функция предоставляет точку для реализации операций очистки (например, закрытие файловые дескрипторы, освобождение внутренних указателей или ведение журнала) без явных изменений в клиентском коде: иногда это хорошая модель для последовательного принятия
    • в этом псевдо-OO стиле программирования естественно хотеть что-то вроде WonderfulObject* p = WonderfulObject_AllocConstructor(); - это возможно, когда "конструктор" является функцией, возвращающей malloc-ed память (поскольку память остается выделенной после того, как функция возвращает значение, которое будет храниться в p), но не если "конструктор" использует alloca
      • макрос версии WonderfulObject_AllocConstructor может достичь этого, но "макросы являются злом" в том, что они могут конфликтовать друг с другом и не-макро-кодом и создавать непреднамеренные замены и последующие труднодиагностируемые проблемы
    • отсутствует free деятельность может быть обнаружена ValGrind, очищает etc. но отсутствующие вызовы "деструктора" не всегда могут быть обнаружены вообще - одно очень незначительное преимущество с точки зрения обеспечения предполагаемого использования; некоторые 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
    }
}

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

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

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

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

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

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

вот почему:

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

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

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

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

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

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

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

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

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

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

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

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

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

alloca() (или VLAs) может быть только правильный инструмент для работы.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

_alloca block on the stack

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

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

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

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

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

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

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

одна ловушка с alloca Это longjmp перематывает его.

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

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

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

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

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

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

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

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

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

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

    (...)
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

сравниваем:

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

С:

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

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