Это было частично основано на дизайне процессора.
В 1980-х годах большинство компьютеров имели 16-битную архитектуру, поэтому указатель мог обращаться к виртуальному адресному пространству размером 2 16 (65536) байтов.
Это, естественно, считалось слишком ограничительным.
Когда сегмент коды отделен от других, вы можете иметь 2 16 байт ценности коды (инструкция) и 2 16 байт данных, что меньше плохо.
«Пространство данных» программы / процесса (используя термины в самом широком смысле) может включать в себя
- Инициализированные данные, в том числе
- Строки (например,
printf("Hello, world\n");
),
- Инициализированные переменные (например,
int i = 42;
) и
- В некоторых случаях константы, используемые в коде (например,
y = x + 17;
),
- Неинициализированные переменные (например,
int j;
char mydata[80];
в статическом / глобальном контекстах),
- Динамически выделяемая память (
malloc
, new
) и
- Стек (аргументы функций, информация о сохранении / восстановлении и локальные переменные).
Возможно, я что-то пропускаю, но регистры на самом деле не учитываются (это, по сути, особый случай неинициализированных переменных), и общая память выходит за рамки этого обсуждения.
Обратите внимание, что из вышеперечисленного в исполняемый файл необходимо сохранять только инициализированные данные - пространство для неинициализированных переменных, а стек автоматически выделяется ОС при выполнении программы.
Таким образом, компиляторы должны были отслеживать инициализированные данные и неинициализированные переменные отдельно.
Другая проблема заключается в том, что процессор Intel 8086 (прапрадедушка Pentium) требовал, чтобы каждый сегмент был одним большим непрерывным блоком памяти.
Ядро Unix состоит из одного большого блока кода и одного большого блока данных (инициализированных данных и неинициализированных переменных), но для каждого процесса он использует отдельный стек.
Поэтому для 8086 требовалась возможность иметь стек в другом сегменте, чем все остальные данные.
В интернете много информации об этом.
Например, в Википедии: