Графические устройства

         

Сегментирование текстов программ

Составленные на языке Макроассемблера программы обязательно сегментируются. В простейшем случае вся программа может состоять только из одного сегмента. Подобная программа была приведена в примере 4.1.

Описание и расположение сегментов. В большинстве случаев текст программы состоит, по крайней мере, из трех сегментов, содержащих область стека, раздел данных и коды. В примере Б.1 показана общая структура программы, содержащей три основных сегмента.

Пример Б.1. Структура программы, состоящей из трех сегментов

stack Segment word stack "stack"; начало стекового сегмента
db 10Oh dup (?) ; размер области стека 10Oh байтов stack Ends ; конец стекового сегмента .
data Segment ; начало сегмента данных
; в этом сегменте располагается описание
используемых в программе данных data Ends : конец сегмента данных
code Segment ; начало кодового сегмента ; в этом сегменте располагается текст основной ; программы и входящих в нее подпрограмм code Ends ; конец


кодового сегмента
END ; конец текста программы

Из текста примера Б.1 видно, что описание сегмента открывает директива Segment, а закрывает Ends. Перед обеими директивами указывается одинаковая метка, символ "двоеточие" в данном случае отсутствует. Метка указывается дважды для того, чтобы при работе с вложенными сегментами Макроассемблер мог определить, какой из них закрывает данная директива. Формально вложенные сегменты допустимы, но особых преимуществ их использование не дает.

После директивы segment могут располагаться параметры, которые обычно не нужны. Исключением является стековый сегмент, если не указать параметр stack, то компоновщик (link.exe) выдаст сообщение об его отсутствии в теле задачи. Это не аварийное, а предупреждающее сообщение, т. к. стековый сегмент действительно может отсутствовать. Однако если он явно описан, а компоновщик его не обнаружил, значит, в тексте программы допущена ошибка, и ее надо исправить.

Последовательность расположения сегментов в тексте программы не имеет значения ни для Макроассемблера, ни для компоновщика, ни для DOS. Поэтому разработчик располагает их в нужном ему порядке, а основным критерием является наглядность текста программы и удобство его анализа. Правда в некоторых случаях бывает важно знать, какой сегмент расположен последним в тексте построенной задачи. Такой случай будет рассмотрен в следующем разделе.

Расположение сегментов в задаче. Макроассемблер может располагать сегменты в тексте будущей задачи либо в порядке их описания в исходной программе, либо по именам в алфавитном порядке. Вариант расположения можно выбрать с помощью директив .SEQ и .ALPHA (в обоих случаях указание точки обязательно), которые располагаются перед описанием первого сегмента. Директива .SEQ устанавливает расположение сегментов в порядке их описания, а директива .ALPHA— в алфавитном порядке. Обычно по умолчанию сегменты располагаются в порядке их описания.
Реальное расположение сегментов в построенной задаче компоновщик выводит в файл, имя которого надо указать в ответ на подсказку "List file >", по умолчанию такой файл имеет тип тар. Если в ответ на подсказку вы введете конкретное имя, то получите файл, содержащий имена, адреса и размеры описанных в программе сегментов.
Специальные директивы описания сегментов. В документации на Макроассемблер для описания сегментов, содержащих константы, данные, коды и стек, рекомендуется использовать специальные директивы: .CONST, .DATA, .CODE, .STACK (указание точки обязательно), которые автоматически формируют все параметры сегмента. Им обязательно должна предшествовать директива .MODEL с указанием названия используемой модели памяти (пример Б.2). Допустимо использование следующих имен: small, compact, medium, large и huge.

Перечисленные типы моделей памяти поддерживают компиляторы таких языков программирования, как Паскаль, Си, Фортран и некоторые другие, что и объясняет введение директивы .MODEL. В MASM 6.0 специально для защищенного режима введена еще одна модель — flat.

Пример Б.2. Специальные директивы описания основных сегментов |

Dosseg ; задает расположение сегментов
.Model small ; описание модели памяти
.Stack 100h ; описание стекового сегмента
.Data ; начало сегмента данных
; В этом сегменте располагается описание
; используемых в программе данных
^ , . Code ; начало сегмента кодов
; В этом сегменте располагается текст основной
; программы и входящих в нее подпрограмм
END ; конец текста программы

В примере Б.2 директивы .stack, .Data, .code указывают начало соответствующего сегмента, директива Ends при этом не нужна. Признаком конца текущего сегмента является начало следующего или директива END, завершающая текст программы, поэтому вложение сегментов исключено. В тексте программы, кроме указанных сегментов, могут использоваться другие, описанные обычным способом.

Перед специальными директивами имена сегментов не указываются, но они могут понадобиться для использования в программе. Макроассемблер присваивает стековому сегменту имя stack, сегменту данных — имя _data (нижняя черта обязательна). Имя сегмента кодов зависит от модели памяти, например, при моделях small и compact оно будет _text. Присвоенные сегментам имена можно посмотреть в карте памяти, которую формирует компоновщик при указании файла с типом тар (см. выше).

Для управления последовательностью расположения сегментов в примере Б.2 использована директива Dosseg, которая размещает сегменты в соответствии с соглашениями DOS. Это значит, что первым в теле задачи будет расположен кодовый, а последним — стековый сегмент. Если сегментов всего три, то данные будут размещены после кодов, перед областью стека. Дополнительные сегменты располагаются между кодами и данными.

Работа с именами сегментов

Макроассемблер рассматривает имя сегмента как константу (а не как переменную), ее прямая пересылка в сегментный регистр невозможна, поэтому приходится использовать регистр-посредник. Например, в большинстве случаев в начале выполнения задачи надо определить содержимое сегментного регистра DS, поместив в него код сегмента данных. Это можно сделать с помощью двух следующих команд:

start: mov ax, data ; ax = код сегмента данных mov ds, ax ; ds = ax

Этот пример составлен исходя из предположения, что сегмент данных описан так, как приведено в примере Б.1. Если же он описан, как в примере Б.2, то в первой команде надо изменить имя data на _data или @data.

Макроассемблер позволяет определить код сегмента тремя разными способами: указать его явно в виде числа, указать имя сегмента или указать имя метки, расположенной в нужном сегменте. В последнем случае перед именем метки помещается оператор seg. Следующие три команды иллюстрируют сказанное:

mov ax, 0400h ; указание конкретного числового значения
mov ax, job ; явное указание имени сегмента
mov ax, seg buf ; косвенная ссылка на сегмент по имени метки

Команды первого типа нужны при обращении к сегментам, расположенным вне тела программы, например к области данных BIOS.

Команды второго типа применяются при работе с сегментами, описанными в тексте программы. Точное значение таких сегментов заранее неизвестно, его вычисляет DOS при загрузке программы в память для выполнения.

Команды третьего типа полезны в тех случаях, когда метка является внешним именем, не описанным в программе, и нужно узнать, какому сегменту она принадлежит.
После того, как с помощью одной из этих команд значение сегмента окажется в регистре ах, оно копируется в нужный сегментный регистр командой пересылки, как это было показано выше на примере определения содержимого регистра ds.

Резервирование пространства памяти. Большинство графических задач нуждается в определенном пространстве ОЗУ для размещения буферов различного назначения. В простейшем случае для резервирования требуемого пространства памяти в текст программы включаются дополнительные сегменты.

Это можно сделать, например, так:

Job Segment ; начало сегмента
buffer db 65536 dup (?) ; директива резервирует 65536 байтов
Job Ends ; конец сегмента

В приведенном примере сегмент становится частью тела задачи и увеличивает его размер на соответствующее количество байтов. Поэтому такой способ распределения пространства ОЗУ является вспомогательным и применять его можно только в порядке исключения.

Более гибкий и универсальный вариант заключается в том, что для размещения буферов различного назначения пространство оперативной памяти не резервируется в исходном тексте программы, а распределяется в процессе выполнения задачи.


Содержание раздела