Выбрать главу

Программа 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 dump) является образом запущенного процесса в памяти, который создаётся при неожиданном завершении процесса. Позже этот дамп может быть использован для отладки Unix-системы, называют это файл core, а системы GNU/Linux — core.pid, где pid — ID потерпевшего крушения процесса — Примеч. автора.

вернуться

41

Описание здесь намеренно упрощено. Запущенные программы занимают значительно больше места, чем указывает программа size, поскольку разделяемые библиотеки включены в адресное пространство. Также сегмент данных будет расти по мере выделения программной памяти — Примеч. автора.