Эта программа распечатывает местонахождение двух функций 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. Как вы обозначаете конец файла? Как вы указываете, что возникла ошибка ввода/вывода? Должна ли ваша функция сообщать об ошибках? Объясните, почему.