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
Address of afunc: 0x80484a8
Stack Locations:
Stack level 1: address of stack_var: 0xbffff864
Stack level 2: address of stack_var: 0xbffff844
/* Стек растет вниз */
Start of alloca'ed array: 0xbffff860
End of alloca'ed array: 0xbffff87f
/* Адреса находятся в стеке */
Data Locations:
Address of data_var: 0x80496b8
BSS Locations:
Address of bss_var: 0x80497c4
/* BSS выше инициализированных данных */
Heap Locations:
Initial end of heap: 0x80497c8
/* Куча непосредственно над BSS */
New end of heap: 0x80497e8
/* И растет вверх */
Final end of heap: 0x80497d8
/*
Адресные пространства можно сокращать */
3.3. Резюме
• У каждой программы Linux и (Unix) есть различные области памяти. Они хранятся в разных частях файла исполняемой программы на диске. Некоторые из секций загружаются при запуске программы в одну и ту же область памяти. Все запушенные экземпляры одной и той же программы разделяют исполняемый код (сегмент текста). Программа
size
показывает размеры различных областей переместимых объектных файлов и полностью скомпонованных исполняемых файлов.
• В адресном пространстве запушенной программы могут быть дыры, а размер адресного пространства может изменяться при выделении и освобождении памяти. На современных системах адрес 0 не является частью адресного пространства, поэтому не пытайтесь разыменовывать указатели
NULL
.
• На уровне языка С память выделяется с помощью одной из функций
malloc
,
calloc
или
realloc
. Память освобождается с помощью
free
. (Хотя с помощью
realloc
можно делать все, использование ее таким образом не рекомендуется.) Освобожденная память обычно не удаляется из адресного пространства; вместо этого она используется повторно при последующих выделениях.
• Необходимо предпринять чрезвычайные меры осторожности в следующих случаях
• освобождать лишь память, выделенную с помощью соответствующих процедур,
• освобождать память один и только один раз,
• освобождать неиспользуемую память и
• не допускать «утечки» динамически выделяемой памяти.
• POSIX предоставляет для удобства функцию
strdup
, a GLIBC предоставляет функции
getline
и
getdelim
для чтения строк произвольной длины. Функции интерфейса низкоуровневых системных вызовов
brk
и
sbrk
предоставляют непосредственный, но примитивный доступ к выделению и освобождению памяти. Если вы не создаете свой собственный распределитель памяти, следует избегать их. Существует функция
alloca
для выделения памяти в стеке, но ее использование не рекомендуется. Подобно умению распознавать ядовитый плющ, про нее нужно знать лишь для того, чтобы избегать ее.
Упражнения
1. Начав со структуры —
struct line {
size_t buflen;
char *buf;
FILE* fp;
};
— напишите свою собственную функцию
readline
, которая будет читать строки любой длины. Не беспокойтесь о строках, продолженных с помощью обратного слеша. Вместо использования
fgetc
для чтения строк используйте
getc
для чтения одного символа за раз.
2. Сохраняет ли ваша функция завершающий символ конца строки? Объясните, почему.
3. Как ваша функция обрабатывает строки, оканчивающиеся CR-LF?
4. Как вы инициализируете структуру? В отдельной процедуре? С помощью документированных условий для определенных значений в структуре?
5. Как вы обозначаете конец файла? Как вы указываете, что возникла ошибка ввода/вывода? Должна ли ваша функция сообщать об ошибках? Объясните, почему.
6. Напишите программу, которая использует вашу функцию для ее тестирования, а также другую программу, создающую входные данные для первой программы. Протестируйте функцию.