/* Заголовок в GNU/Linux, возможно, не на всех Unix-системах */
#include <alloca.h> /* Обычный */
void *alloca(size_t size);
Функция alloca()
выделяет size
байтов из стека. Хорошо, что выделенная память исчезает после возвращения из функции. Нет необходимости явным образом освобождать память, поскольку это осуществляется автоматически, как в случае с локальными переменными.
На первый взгляд, alloca()
выглядит чем-то типа панацеи для программистов, можно выделять память, о которой можно вовсе не беспокоиться. Подобно Темной Стороне Силы, это, конечно, привлекает. И подобным же образом этого нужно избегать по следующим причинам:
• Функция не является стандартной; она не включена ни в какой стандарт, ни в ISO, ни в С или POSIX.
• Функция не переносима. Хотя она существует на многих системах Unix и GNU/Linux, она не существует на не-Unix системах. Это проблема, поскольку код часто должен быть многоплатформенным, выходя за пределы просто Linux и Unix.
• На некоторых системах alloca()
невозможно даже реализовать. Весь мир не является ни процессором Intel x86, ни GCC.
• Цитируя справку[45] (добавлено выделение): «Функция alloca
зависит от машины и от компилятора. На многих системах ее реализация ошибочна. Ее использование не рекомендуется».
• Снова цитируя справку: «На многих системах alloca
не может быть использована внутри списка аргументов вызова функции, поскольку резервируемая в стеке при помощи alloca
память оказалась бы в середине стека в пространстве для аргументов функции».
• Она потворствует неряшливому программированию. Тщательная и корректная работа с памятью не сложна; вам просто нужно подумать о том, что вы делаете, и планировать заранее.
GCC обычно использует встроенную версию функции, которая действует с использованием внутритекстового (inline) кода. В результате есть другие последствия alloca()
. Снова цитируя справку:
Факт, что код является внутритекстовым (inline), означает, что невозможно получить адрес этой функции или изменить ее поведение путем компоновки с другой библиотекой.
Внутритекстовый код часто состоит из одной инструкции, подгоняющей указатель стека, и не проверяет переполнение стека. Поэтому нет возврата NULL
при ошибке.
Справочная страница не углубляется в описание проблемы со встроенной alloca()
GCC. Если есть переполнение стека, возвращаемое значение является мусором. И у вас нет способа сообщить об этом! Это упущение делает невозможным использование GCC alloca()
в устойчивом коде.
Все это должно убедить вас избегать alloca()
в любом новом коде, который вы пишете. В любом случае, если приходится писать переносимый код с использованием malloc()
и free()
, нет причины в использовании также и alloca()
.
3.2.5. Исследование адресного пространства
Следующая программа, ch03-memaddr.c
, подводит итог всему, что мы узнали об адресном пространстве. Она делает множество вещей, которые не следует делать на практике, таких, как вызовы alloca()
или непосредственные вызовы brk()
и sbrk()
.
1 /*
2 * ch03-memaddr.с --- Показать адреса секций кода, данных и стека,
3 * а также BSS и динамической памяти.
4 */
5
6 #include <stdio.h>
7 #include <malloc.h> /* для определения ptrdiff_t в GLIBC */
8 #include <unistd.h>
9 #include <alloca.h> /* лишь для демонстрации */
10
11 extern void afunc(void); /* функция, показывающая рост стека */
12
13 int bss_var; /* автоматически инициализируется в 0, должна быть в BSS */
14 int data_var = 42; /* инициализируется в не 0, должна быть
15 в сегменте данных */
16 int
17 main(int argc, char **argv) /* аргументы не используются */
18 {
19 char *p, *b, *nb;
20
21 printf("Text Locations:\n");
22 printf("\tAddress of main: %p\n", main);
23 printf("\tAddress of afunc: %p\n", afunc);
24
25 printf("Stack Locations.\n");
26 afunc();
27
28 p = (char*)alloca(32);
29 if (p != NULL) {
30 printf("\tStart of alloca()'ed array: %p\n", p);
31 printf("\tEnd of alloca()'ed array: %p\n", p + 31);
32 }
33
34 printf("Data Locations:\n");
35 printf("\tAddress of data_var: %p\n", &data_var);
36
37 printf("BSS Locations:\n");
38 printf("\tAddress of bss_var: %p\n", &bss_var);
39
40 b = sbrk((ptrdiff_t)32); /* увеличить адресное пространство */
41 nb = sbrk((ptrdiff_t)0);
42 printf("Heap Locations:\n");
43 printf("\tInitial end of heap: %p\n", b);
44 printf("\tNew end of heap: %p\n", nb);
45
46 b = sbrk((ptrdiff_t)-16); /* сократить его */
47 nb = sbrk((ptrdiff_t)0);
48 printf("\tFinal end of heap: %p\n", nb);
49 }
50
51 void
52 afunc(void)
53 {
54 static int level = 0; /* уровень рекурсии */
55 auto int stack_var; /* автоматическая переменная в стеке */
56
57 if (++level == 3) /* избежать бесконечной рекурсии */
58 return;
59
60 printf("\tStack level %d: address of stack_var: %p\n",
61 level, &stack_var);
62 afunc(); /* рекурсивный вызов */
63 }
Эта программа распечатывает местонахождение двух функций main()
и afunc()
(строки 22–23). Затем она показывает, как стек растет вниз, позволяя afunc()
(строки 51–63) распечатать адреса последовательных экземпляров ее локальной переменной stack_var
. (stack_var
намеренно объявлена как auto
, чтобы подчеркнуть, что она находится в стеке.) Затем она показывает расположение памяти, выделенной с помощью alloca()
(строки 28–32). В заключение она печатает местоположение переменных данных и BSS (строки 34–38), а затем памяти, выделенной непосредственно через sbrk()
(строки 40–48). Вот результаты запуска программы на системе Intel GNU/Linux:
$ ch03-memaddr
Text Locations:
Address of main: 0x804838c