Рис. 3.1. Адресное пространство Linux/Unix
Хотя перекрывание стека и кучи теоретически возможно, операционная система предотвращает этот случай, и любая программа, пытающаяся это сделать, напрашивается на неприятности. Это особенно верно для современных систем, в которых адресные пространства большие и интервал между верхушкой стека и концом кучи значителен. Различные области памяти могут иметь различную установленную на память аппаратную защиту. Например, сегмент текста может быть помечен «только для исполнения», тогда как у сегментов данных и стека разрешение на исполнение может отсутствовать. Такая практика может предотвратить различные виды атак на безопасность. Подробности, конечно, специфичны для оборудования и операционной системы, и они могут со временем меняться. Стоит заметить, что стандартные как С, так и C++ позволяют размещать элементы с атрибутом
const
в памяти только для чтения. Сводка взаимоотношений различных сегментов приведена в табл. 3.1.
Таблица 3.1. Сегменты исполняемой программы и их размещение
Память программы | Сегмент адресного пространства | Секция исполняемого файла |
Код | Text | Text |
Инициализированные данные | Data | Data |
BSS | Data | BSS |
Куча | Data | |
Стек | Stack | |
Программа
size
распечатывает размеры в байтах каждой из секций text, data и BSS вместе с общим размером в десятичном и шестнадцатеричном виде. (Программа
ch03-memaddr.с
показана далее в этой главе; см. раздел 3.2.5 «Исследование адресного пространства».)
$ <b>cc -o ch03-memaddr.с -о ch03-memaddr</b> /* Компилировать программу */
$ <b>ls -l ch03-memaddr</b> /* Показать общий размер */
-rwxr-xr-x 1 arnold devel 12320 Nov 24 16:45 ch03-memaddr
$ <b>size ch03-memaddr</b> /* Показать размеры компонентов */
text data bss dec hex filename
1458 276 8 1742 6ce ch03-memaddr
$ <b>strip ch03-memaddr</b> /* Удалить символы */
$ <b>ls -l ch03-memaddr</b> /* Снова показать общий размер */
-rwxr-xr-x 1 arnold devel 3480 Nov 24 16:45 ch03-memaddr
$ <b>size ch03-memaddr</b> /* Размеры компонентов не изменились */
text data bss dec hex filename
1458 276 8 1742 6ce ch03-memaddr
Общий размер загруженного в память из файла в 12 320 байтов всего лишь 1742 байта. Большую часть этого места занимают символы (symbols), список имен переменных и функций программы. (Символы не загружаются в память при запуске программы.) Программа
strip
удаляет символы из объектного файла. Для большой программы это может сохранить значительное дисковое пространство ценой невозможности отладки дампа ядра [40], если таковой появится (На современных системах об этом не стоит беспокоиться, не используйте
strip
.) Даже после удаления символов файл все еще больше, чем загруженный в память образ, поскольку формат объектного файла содержат дополнительные данные о программе, такие, как использованные разделяемые библиотеки, если они есть. [41]
Наконец, упомянем потоки (threads), которые представляют несколько цепочек исполнения в рамках единственного адресного пространства. Обычно у каждого потока имеется свой собственный стек, а также способ получения локальных данных потока, т.е. динамически выделяемых данных для персонального использования этим потоком. Мы больше не будем рассматривать в данной книге потоки, поскольку это является продвинутой темой.
3.2. Выделение памяти
Четыре библиотечные функции образуют основу управления динамической памятью С Мы опишем сначала их, затем последуют описания двух системных вызовов, поверх которых построены эти библиотечные функции. Библиотечные функции С, в свою очередь, обычно используются для реализации других выделяющих память библиотечных функций и операторов C++
new
и
delete
.
Наконец, мы обсудим функцию, которую часто используют, но которую мы не рекомендуем использовать.
3.2.1. Библиотечные вызовы:
malloc()
,
calloc()
,
realloc()
,
free()
Динамическую память выделяют с помощью функций
malloc()
или
calloc()
. Эти функции возвращают указатели на выделенную память. Когда у вас есть блок памяти определенного первоначального размера, вы можете изменить его размер с помощью функции
realloc()
. Динамическая память освобождается функцией
free()
.
Отладка использования динамической памяти сама по себе является важной темой. Инструменты для этой цели мы обсудим в разделе 15.5.2 «Отладчики выделения памяти».
3.2.1.1. Исследование подробностей на языке С
Вот объявления функций из темы справки GNU/Linux malloc(3):
#include <stdlib.h> /* ISO С */
void *calloc(size_t nmemb, size_t size);
/* Выделить и инициализировать нулями */
void *malloc(size_t size);
/* Выделить без инициализации */
void free(void *ptr);
/* Освободить память */
void *realloc(void *ptr, size_t size);
/* Изменить размер выделенной памяти */
Функции выделения памяти возвращают тип
void*
. Это бестиповый или общий указатель, все, что с ним можно делать — это привести его к другому типу и назначить типизированному указателю. Примеры впереди.
Тип
size_t
является беззнаковым целым типом, который представляет размер памяти. Он используется для динамического выделения памяти, и далее в книге мы увидим множество примеров его использования. На большинстве современных систем
size
_t является
unsigned long
, но лучше явно использовать
size_t
вместо простого целого типа
unsigned
.