Сегмент данных
Сегмент данных — это область оперативной памяти, выделяемая программе для хранения глобальных и статических переменных, а также констант времени компиляции. В контексте архитектуры компьютерных систем и программирования сегмент данных является одной из ключевых частей адресного пространства процесса, наряду с сегментом кода (текстовым сегментом), стеком и кучей. Он характеризуется фиксированным размером, определяемым на этапе компиляции, и доступен для чтения и записи (за исключением подсегмента констант, который может быть защищён от записи).
Структура и размещение в памяти
Сегмент данных традиционно располагается в виртуальном адресном пространстве процесса после сегмента кода. В типичной модели памяти программы на языках C/C++ он делится на две основные части:
- Инициализированные данные (.data) — содержат глобальные и статические переменные, которым при объявлении присвоено начальное значение, отличное от нуля. Например,
int global_var = 42;илиstatic int counter = 10;. Эти значения копируются из образа исполняемого файла в оперативную память при запуске программы. - Неинициализированные данные (.bss) — содержат глобальные и статические переменные, которые не имеют явной инициализации или инициализированы нулём. Например,
int global_uninit;илиstatic int buffer[1000];. В образе исполняемого файла для этой области не выделяется место (только запись о размере), а при загрузке программы она обнуляется операционной системой.
Отдельно может выделяться сегмент только для чтения (.rodata), в котором хранятся константы (строковые литералы, константные массивы и структуры, определённые с ключевым словом const). В некоторых архитектурах и операционных системах этот подсегмент помещается в сегмент кода или в отдельную область с защитой от записи.
Отличия от других областей памяти
- Сегмент кода (.text) — содержит исполняемые инструкции процессора. Обычно доступен только для чтения и выполнения, но не для записи. Сегмент данных, напротив, доступен для записи (кроме .rodata).
- Стек — используется для хранения локальных переменных, параметров функций и адресов возврата. Размер стека динамически изменяется во время выполнения (в пределах заданного лимита), а данные в нём существуют только в рамках вызова функции. Сегмент данных статичен и существует на протяжении всей жизни процесса.
- Куча (heap) — область для динамического выделения памяти (функции
malloc,new,calloc). Управляется программистом или сборщиком мусора, имеет переменный размер. Сегмент данных не предназначен для динамического управления памятью.
Жизненный цикл и инициализация
При запуске исполняемого файла загрузчик операционной системы (например, ld-linux.so в Linux или загрузчик Windows) выполняет следующие действия с сегментом данных:
- Считывает из заголовка исполняемого файла (формат ELF, PE, Mach-O) информацию о расположении и размерах секций .data, .bss и .rodata.
- Отображает секцию .data в виртуальную память процесса, копируя в неё начальные значения из файла на диске.
- Выделяет память для секции .bss и заполняет её нулями.
- Устанавливает права доступа: для .data — чтение и запись; для .rodata — только чтение; для .text — чтение и выполнение.
Переменные в сегменте данных существуют до завершения программы. Они доступны из любой функции текущего процесса (если не скрыты областью видимости), что делает их удобным инструментом для обмена данными между функциями, но одновременно источником потенциальных проблем с многопоточностью и безопасностью.
Примеры содержимого
Рассмотрим фрагмент кода на языке C:
```c
include <stdio.h>
int global_init = 100; // .data int global_uninit; // .bss const char *message = "Hello"; // message — .data, строка "Hello" — .rodata static int static_var = 5; // .data
int main() { static int local_static = 0; // .bss (обнуляется) int local_auto = 10; // стек return 0; } ```
global_initиstatic_varпопадут в секцию .data, так как имеют ненулевые начальные значения.global_uninitиlocal_static— в .bss.- Строковый литерал
"Hello"— в .rodata (только для чтения). Попытка изменить его приведёт к ошибке сегментации. - Переменная
local_autoразмещается на стеке и не имеет отношения к сегменту данных.
Значение в различных парадигмах программирования
В процедурных языках (C, Pascal, Fortran) сегмент данных является основным способом хранения глобального состояния программы. В объектно-ориентированных языках (C++, Java, C#) глобальные переменные в чистом виде используются реже, однако статические поля классов и статические локальные переменные также размещаются в сегменте данных. Например, в Java статические поля класса хранятся в области Metaspace (аналог сегмента данных в контексте виртуальной машины), а в C++ — непосредственно в .data или .bss.
В функциональных языках (Haskell, Erlang) глобальные изменяемые переменные обычно отсутствуют или ограничены, что снижает роль сегмента данных в таких системах.
Безопасность и уязвимости
Сегмент данных может становиться объектом атак. Наиболее известные проблемы:
- Переполнение буфера в сегменте данных — если программа записывает данные за границы статического массива, злоумышленник может перезаписать соседние глобальные переменные, изменив логику работы программы.
- Гонки данных (data races) — одновременный доступ нескольких потоков к одной глобальной переменной без синхронизации приводит к неопределённому поведению.
- Атаки на .rodata — хотя секция констант защищена от записи, ошибки в компиляторе или аппаратные сбои (Rowhammer) могут позволить её модифицировать.
Для защиты применяются методы: рандомизация адресного пространства (ASLR), запрет исполнения кода в сегменте данных (NX-бит), статический анализ кода и использование безопасных функций копирования памяти.
Особенности в разных операционных системах
- Linux/Unix — использует формат ELF. Сегмент данных может быть расширен за счёт системного вызова
brk()илиmmap(), но традиционно его размер фиксирован. Секции .data и .bss явно описаны в заголовках программы. - Windows — использует формат PE. Аналогом сегмента данных являются секции
.dataи.bss(или.idataдля импортируемых данных). Windows также поддерживает секции с общими данными (shared data) для межпроцессного взаимодействия. - Встраиваемые системы — сегмент данных часто размещается в ОЗУ или flash-памяти. На этапе загрузки прошивка копирует инициализированные данные из ПЗУ в ОЗУ, а .bss обнуляется в startup-коде.
Источники
- Брайан Керниган, Деннис Ритчи. «Язык программирования C», 2-е издание.
- Эндрю Таненбаум. «Архитектура компьютера», 6-е издание.
- Документация по формату ELF (TIS Committee, 1995).
- Стандарт POSIX.1-2017 (раздел «Process Memory Layout»).
- Материалы курса «Операционные системы» (Computer Science, Стэнфордский университет).
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →