Добавить в корзинуПозвонить
Найти в Дзене

SEGMENT в ассемблере: Как нарезать память на «колбасу» для процессора

Когда новички слышат слово «ассемблер», у них перед глазами возникают ряды цифр, страшные команды вроде MOV и ADD, и, конечно, сегменты. А ведь сегментация — это просто. Готовьтесь, сейчас мы на пальцах разберем директиву SEGMENT и поймем, почему без нее ваш код даже не запустится. Представьте, что оперативная память — это огромное поле. А процессор — фермер, который по нему ходит. Чтобы не заблудиться, он делит поле на участки: в одном сегменте у нас морковка (код программы), в другом — кабачки (данные), в третьем — место для шашлыков (стек). В реальных архитектурах (например, x86 в реальном режиме) процессор физически не может адресовать всю память одним числом из-за разрядности регистров (то есть размер регистра просто не позволяет поместить в него адрес памяти, если её “слишком” много). Вот тут и появляется сегментация. Директива SEGMENT — это ваш колышек и веревка. Вы говорите ассемблеру: «Отсюда и до сюда — это мой сегмент кода, туда не лезь». Пишется это просто (пример для TASM/
Оглавление

Когда новички слышат слово «ассемблер», у них перед глазами возникают ряды цифр, страшные команды вроде MOV и ADD, и, конечно, сегменты. А ведь сегментация — это просто. Готовьтесь, сейчас мы на пальцах разберем директиву SEGMENT и поймем, почему без нее ваш код даже не запустится.

Зачем процессору «огород городить»?

Представьте, что оперативная память — это огромное поле. А процессор — фермер, который по нему ходит. Чтобы не заблудиться, он делит поле на участки: в одном сегменте у нас морковка (код программы), в другом — кабачки (данные), в третьем — место для шашлыков (стек).

В реальных архитектурах (например, x86 в реальном режиме) процессор физически не может адресовать всю память одним числом из-за разрядности регистров (то есть размер регистра просто не позволяет поместить в него адрес памяти, если её “слишком” много). Вот тут и появляется сегментация.

Директива SEGMENT — это ваш колышек и веревка. Вы говорите ассемблеру: «Отсюда и до сюда — это мой сегмент кода, туда не лезь».

Синтаксис древних

Пишется это просто (пример для TASM/MASM):

Что здесь произошло?

  1. MyCode SEGMENT — Открываем сегмент с именем MyCode. Говорим компилятору: «Ставь здесь метку начала области памяти».
  2. ASSUME CS:MyCode — Даём понять, что сегментный регистр CS (Code Segment) указывает именно на этот наш кусок. Это подсказка для компилятора, чтобы он правильно считал смещения.
  3. ... ENDS — Ставим забор. Сегмент закончен.

Важно: В современных плоских моделях памяти (например, в x86-64 под Windows/Linux) сегментация почти умерла. Но в учебных целях, для DOS-программ, загрузчиков (bootloader) и микроконтроллеров директива SEGMENT жива и нужна.

Типы сегментов: стройбатальон памяти

Просто огородить участок мало. Надо сказать, что там будет расти. Для этого используют атрибуты:

DataSEG SEGMENT PARA PUBLIC 'DATA' USE16
message db "Hello, World!", '$'
DataSEG ENDS

Смотрим на «специи»:

  • PARA (параграф) — Выравнивание. Просит компилятор начинать сегмент строго с адреса, кратного 16. Это ускоряет доступ (раньше было критично, сейчас — дань традиции).
  • PUBLIC — Связывание. Если ваша программа разбита на несколько файлов (кирпичиков), PUBLIC укажет линковщику склеить все сегменты с одинаковым именем в один большой.
  • 'DATA' (класс) — Сортировка. Линковщик группирует сегменты одного класса друг за другом. Сначала 'CODE', потом 'DATA', потом 'STACK'.
  • USE16 / USE32 — Разрядность. Определяем, в 16-битном режиме работает этот участок или в 32-битном (нужно для процессоров, начиная с 386).

Самый частый вопрос: Зачем ASSUME?

ASSUME — это вечно пугающая директива. Просто запомните: процессор сам по себе не знает, какой сегмент куда воткнут. А ассемблеру нужно уметь превращать mov ax, [var] в машинный код.

Если вы напишете:

-2

Ассемблер выдаст ошибку. Ему нужно сказать: «Переменная count лежит относительно регистра DS (Data Segment)». Поэтому в начале кода пишут:

assume DS:DataSEG
mov ax, DataSEG
mov ds, ax ; Грузим реальный адрес сегмента в регистр DS
mov ax, count ; Теперь ОК!

Подводные камни

  1. Стек — тоже сегмент.
    Вы должны объявить сегмент стека первым, чтобы он не перемешался с данными. Иначе при вызове ret программа улетит в адреса данных и упадет с ошибкой GPF (если вы в защищенном режиме).
  2. Модели памяти.
    В DOS были модели TINY (все в одном сегменте, код+данные = 64Кб), SMALL (один сегмент кода, один данных), LARGE (много сегментов). Директива SEGMENT работает во всех, но в TINY вы можете обойтись вообще без нее.
  3. Современные компиляторы.
    Если вы откроете листинг, сгенерированный GCC для Linux, вы не увидите SEGMENT. Там используются секции (.text, .data) и плоская память. Сегментация эмулируется на уровне ОС (через страничную память), а не в самом процессоре (в 64-битном режиме сегментация сильно урезана).

Живой пример: "Hello world" для DOS (TASM)

-3

Видите? Даже когда мы пишем .data и .code — это всё равно сахар (макросы) над древними директивами SEGMENT.

Итог

SEGMENT — это наследие великой эпохи 16-битных процессоров. Сегодня в веб-разработке или скриптах она вам не нужна. Но если вы:

  • Пишете загрузчик для своей ОС.
  • “Оживляете” старые игры.
  • Изучаете, как железо на самом деле работает с памятью.

То SEGMENT — ваш лучший друг. Она учит главному: в компьютере всё разложено по полочкам. А бардак в сегментах — гарантированный Error #FFF в 3 часа ночи.

Лайк — если после статьи не захотелось удалить все IDE и уйти в DOSBox.
Репост — тому, кто все еще путает CS и DS.

Используйте эту статью как шпаргалку. И да пребудет с вами сила выравнивания!