Перейти к основному содержимому

Память процесса и сегменты

Разработчику Архитектору

Память процесса и сегменты

Зачем это новичку

Когда вы пишете int x = 5; внутри функции и static int counter; снаружи, компилятор кладёт их в разные места оперативной памяти. От этого зависят срок жизни переменной, скорость доступа и типичные баги: «вернул указатель на локальную переменную», «переполнил стек огромным массивом», «забыл free». Карта памяти связывает синтаксис С с тем, что происходит при запуске собранной программы.

Процесс — это запущенная программа со своим виртуальным адресным пространством (ОС выдаёт иллюзию «у меня свой кусок RAM»). Сегмент — логическая область внутри этого пространства с общим назначением (код, данные, стек).

Программа на С после сборки превращается в исполняемый файл. При запуске операционная система загружает его в виртуальное адресное пространство процесса и раскладывает содержимое по логическим областям. Понимание этой карты помогает объяснить, почему глобальные переменные живут всё время работы процесса, локальные исчезают после выхода из функции, а malloc берёт память из другого места, чем int x внутри main.

Общая схема (упрощённо, сверху вниз по типичному расположению в адресном пространстве):

высокие адреса
┌─────────────────┐
│ стек │ локальные переменные, кадры вызовов
├─────────────────┤
│ ↓ │ растут навстречу друг другу
│ ↑ │
├─────────────────┤
│ куча │ malloc / calloc / realloc
├─────────────────┤
│ BSS (нулевые │ глобальные и static без явной инициализации
│ глобальные) │
├─────────────────┤
│ data (инициализ.│ глобальные и static с начальными значениями
│ глобальные) │
├─────────────────┤
│ text (код) │ машинные инструкции, константы только для чтения
└─────────────────┘
низкие адреса

На практике порядок и границы зависят от ОС и формата исполняемого файла (ELF, PE, Mach-O), но роли областей одинаковы.

Куда попадает переменная — быстрая таблица

Вы написали в кодеОбластьКогда исчезает
int g = 1; вне функцийdataпри завершении процесса
static int n; вне функцийBSS (ноль по умолчанию)при завершении процесса
int x; внутри mainстекпри выходе из main
char buf[100]; в функциистекпри выходе из функции
malloc(100)кучапосле free или утечка до конца процесса
"hello" в printf("hello")часто read-only (как код)всё время процесса

Сегмент кода (text)

Сюда попадает скомпилированный машинный код функций: main, printf из библиотеки (при статической линковке — внутри образа), пользовательские функции. Область обычно только для чтения и исполнения: запись в код из программы приводит к ошибке доступа (защита от случайных и злонамеренных изменений).

Строковые литералы в классическом С часто тоже размещаются в read-only сегменте:

const char *msg = "Hello";

Попытка изменить msg[0] — неопределённое поведение. Для изменяемого буфера нужен массив char buf[] = "Hello"; в стеке или куче.


Data и BSS

Data хранит глобальные и статические переменные с явной инициализацией на этапе компиляции:

int counter = 10;
static double rate = 3.14;

Их начальные значения записаны в исполняемый файл; при старте процесса загрузчик копирует их в RAM.

BSS (Block Started by Symbol) — область для глобальных и static, которые не инициализированы в исходнике (компилятор считает их нулём):

int total_requests;
static char buffer[4096];

В файле на диске для BSS обычно хранится только размер, а не содержимое — экономия места. При запуске ОС выделяет нулевой блок нужной длины.

ОбластьКогда используетсяЖизненный цикл
dataint g = 1;весь процесс
BSSstatic int n;весь процесс
стекint local; в функциипока активен кадр функции
кучаmalloc(...)до free

Стек

Каждый вызов функции создаёт кадр стека: место под локальные переменные, сохранённые регистры, адрес возврата. При выходе из функции кадр уничтожается — поэтому нельзя возвращать указатель на локальную переменную:

int *bad(void) {
int x = 42;
return &x; /* после return x не существует — UB */
}

Глубина стека ограничена (типично от сотен килобайт до нескольких мегабайт, настраивается ОС). Бесконечная рекурсия или огромные локальные массивы вызывают переполнение стека и аварийное завершение.

Стек растёт в одну сторону, куча — в другую. Между ними свободное пространство; если они сближаются — процесс получает ошибку выделения памяти.


Куча (heap)

Динамическая память через malloc, calloc, realloc берётся из кучи. Подробнее о выделении — в Основах языка С. Отличия от стека:

  • блок живёт, пока не вызван free;
  • размер задаётся во время выполнения;
  • порядок освобождения не обязан быть обратным порядку выделения (в отличие от вложенных кадров стека).

Менеджер кучи в libc обслуживает запросы процесса; крупные блоки ОС может выдавать через mmap. Утечки и фрагментация кучи — типичные проблемы долгоживущих сервисов на С.

Разбор вызова malloc:

int *p = (int *)malloc(10 * sizeof(int));
  • malloc просит у менеджера кучи непрерывный блок байт;
  • возвращает адрес начала блока (тип void *, часто приводят к int *);
  • память не обнуляется (в отличие от calloc);
  • указатель p сам лежит на стеке (если объявлен в функции), а данные — в куче.

Подробнее о malloc / free — в Основах языка С.


Связь с объектным файлом и линковкой

На этапе компоновки линкер собирает секции .text, .data, .bss из объектных файлов и библиотек в единый образ. Символы вроде main получают фиксированные смещения; неразрешённые внешние ссылки (printf из libc) подставляются при линковке.

Инструменты вроде size (Unix) или аналог в IDE показывают вклад каждой единицы трансляции в размер кода и данных — полезно при оптимизации встраиваемых проектов.


Практическая польза при отладке

  • Segmentation fault при разыменовании NULL или «мусорного» указателя — обращение вне разрешённых страниц.
  • Коррупция кучи — часто проявляется позже, в другой функции; отладчики (Valgrind, AddressSanitizer) отслеживают выход за границы блока.
  • Стек и куча — большие буферы лучше выделять в куче или статически (с осторожностью к потокобезопасности), а не как char huge[1_000_000] на стеке.

См. также: Системное программирование на С, Идиомы и обработка ошибок.


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).