Программа size
распечатывает размеры в байтах каждой из секций text, data и BSS вместе с общим размером в десятичном и шестнадцатеричном виде. (Программа ch03-memaddr.с
показана далее в этой главе; см. раздел 3.2.5 «Исследование адресного пространства».)
$ cc -o ch03-memaddr.с -о ch03-memaddr /* Компилировать программу */
$ ls -l ch03-memaddr /* Показать общий размер */
-rwxr-xr-x 1 arnold devel 12320 Nov 24 16:45 ch03-memaddr
$ size ch03-memaddr /* Показать размеры компонентов */
text data bss dec hex filename
1458 276 8 1742 6ce ch03-memaddr
$ strip ch03-memaddr /* Удалить символы */
$ ls -l ch03-memaddr /* Снова показать общий размер */
-rwxr-xr-x 1 arnold devel 3480 Nov 24 16:45 ch03-memaddr
$ size ch03-memaddr /* Размеры компонентов не изменились */
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
.
Тип ptrdiff_t
используется для вычисления адреса в арифметике указателей, как в случае вычисления указателя в массиве:
#define MAXBUF ...
char *p;
char buf[MAXBUF];
ptrdiff_t where;
p = buf;
while (/* некоторое условие */) {
...
p += something;
...
where = p - buf; /* какой у нас индекс? */
}
Заголовочный файл <stdlib.h>
объявляет множество стандартных библиотечных функций С и типов (таких, как size_t
), он определяет также константу препроцессора NULL
, которая представляет «нуль» или недействительный указатель. (Это нулевое значение, такое, как 0 или '((void*)0)
'. Явное использование 0 относится к стилю С++; в С, однако, NULL
является предпочтительным, мы находим его гораздо более читабельным для кода С.)
3.2.1.2. Начальное выделение памяти: malloc()
Сначала память выделяется с помощью malloc()
. Передаваемое функции значение является общим числом затребованных байтов. Возвращаемое значение является указателем на вновь выделенную область памяти или NULL
, если память выделить невозможно. В последнем случае для обозначения ошибки будет установлен errno
. (errno является специальной переменной, которую системные вызовы и библиотечные функции устанавливают для указания произошедшей ошибки. Она описывается в разделе 4.3 «Определение ошибок».) Например, предположим, что мы хотим выделить переменное число некоторых структур. Код выглядит примерно так:
struct coord { /* 3D координаты */
int x, y, z;
} *coordinates;
unsigned int count; /* сколько нам нужно */
40
core
, а системы GNU/Linux — core.
, где
— ID потерпевшего крушения процесса —
41
Описание здесь намеренно упрощено. Запущенные программы занимают значительно больше места, чем указывает программа size
, поскольку разделяемые библиотеки включены в адресное пространство. Также сегмент данных будет расти по мере выделения программной памяти —