Известно, что локальные переменные (и параметры) функций хранятся в стеке, и поэтому автоматически уничтожаются после завершения работы этой функции. Количество выделяемой памяти в стеке для этих целей определяется в момент компиляции, и при выполнении программы фиксировано. К счастью, компилятор gcc и его библиотеки поддерживает удобную возможность динамически выделять память за счет стека с помощью alloca.
[Достоинства alloca]
Вот основной список причин, почему бывает предпочтительно использовать alloca вместо кучи (т. е. вместо malloc [2]):
• Использование alloca очень мало занимает места под код, и этот код работает исключительно быстро. • Поскольку у alloca нет отдельного пула для разного размера блоков, используемое пространство для блоков любого размера может повторно использоваться для любого другого размера. Вызов alloca не приводит к фрагментации памяти, что часто является проблемой для malloc, когда интенсивно выделяются блоки разных размеров. • Не локальные выходы из функции с помощью дальнего перехода (longjmp) автоматически освободит место, выделенное alloca, когда происходит выход из функции, которая вызвала alloca. Это одна из самых важных причин использования alloca.
[Пример использования alloca]
В качестве примера использования alloca здесь приведена функция, которая открывает файл, имя которого склеивается из двух строк, указанных в аргументах, и возвращает дескриптор файла, или вернет -1, если открыть файл не получилось:
Чтобы показать это достоинства alloc, давайте предположим, что у Вас есть функция open_or_report_error наподобие open, которая возвратит дескриптор в случае успешного завершения, но не выполнит возврат в вызывающую функцию, если вызов был неудачен. Если файл не может быть открыт, то эта функция печатает сообщение об ошибке и выйдет на уровень команд вашей программы с помощью longjmp.
int open2 (char *str1, char *str2, int flags, int mode)
{
char *name = (char *) malloc (strlen (str1) + strlen (str2) + 1);
int desc;
if (name == 0)
fatal ("virtual memory exceeded");
stpcpy (stpcpy (name, str1), str2);
desc = open (name, flags, mode);
free (name);
return desc;
}
Давайте поменяем open2 для использования этой подпрограммы open_or_report_error:
int open2 (char *str1, char *str2, int flags, int mode)
{
char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
stpcpy (stpcpy (name, str1), str2);
return open_or_report_error (name, flags, mode);
}
Из-за принципа работы alloca та память, которую она выделила, освободится автоматически даже когда произойдет ошибка, и для этого не нужно делать никаких специальных усилий.
В отличие от предыдущего определения open2 (которое использует malloc и free), могла бы произойти утечка памяти, если изменить функцию таким же образом. Даже если Вы готовы внести больше изменений в malloc-реализацию с целью исправления ситуации, сделать это будет довольно непросто.
Как можно увидеть, на основе alloca реализация намного проще. В большинстве встраиваемых систем с маломощными микроконтроллерами у alloca нет разумной альтернативы. Однако надо иметь в виду, что несмотря на все свои достоинства, использование alloca имеет и недостатки.
[Недостатки alloca]
Недостатки alloca в сравнении с malloc:
• Если Вы попытаетесь выделить больше памяти, чем может предоставить машина, то не получите ясное сообщение об ошибке. Вместо этого скорее всего получится сигнал фатального сбоя, который происходит в случае бесконечной рекурсии. В мире PC возможно это будет сообщение о нарушении доступа к сегменту памяти. С микроконтроллерами это будет или исключение при недопустимой операции, или банальное зависание. • Некоторые не GNU системы не поддерживают alloca, так что программы на основе alloca будет сложнее портировать. Однако в системах с таким недостатком можно использовать несколько более медленную эмуляцию alloca, написанную на языке C.
[Ссылки]
1. Advantages of alloca site:gnu.org. 2. AVR GCC: области памяти и использование malloc(). |