После того как сегменты в программе определены, ассемблер должен
узнать, как будут установлены сегментные регистры во время
выполнения программы. В примере на Фиг. 3.9 всего три сегмента, но
болшая программа может иметь намного больше. Располагая всего
четырьмя сегментными регистрами, большая программа может адресо-
ваться одновременно только к части доступных сегмментов. Ассемблеру
необходимо сообщить, к каким именно сегментам происходит адресация
во время выполнения. Это делается с помощью оператора ASSUME
который описывает для ассемблера установки сегментных регистров.
Программист должен связать каждый сегментный регистр с тем
сегментом, на который тот в данный момент указывает.
Фиг. 3.9 иллюстрирует такие сегментные операции. В этом примере
имеется три сегмента: DATA, BUFFER и CODE. Имена для них выбраны
произвольно. Их выбирает программист, а для ассемблера они не имеют
значения. Например, вы можете назвать сегмент именем CODE, а
использовать его только для данных и наооборот. Лучше всего, конеч-
но, называть сегменты так, чтобы их имена имели какой-то смысл в
данной программе. В нашем примере сегменты DATA и BUFFER оба имеют
внутри ячейку данных. Вряд ли реальная программа будет задавать
сегмент лишь с одной ячейкой памяти, но сейчас это служит для
примера. Если программа обращается к данным во многих участках
адресуемого в 8088 пространства, то ей требуется много определений
сегментов. Например, программа управления устройствами доступа IBM
PC может обращаться к памяти в системной области данных,
устанавливать векторы прерываний в начале памяти и выполняться как
программа в любом другом месте. Каждая из этих областей является
сегментом и должна быть определена в программе.
Утверждение ASSUME на Фиг. 3.9 предписывает ассемблеру работать
с учетом следующей установки сегментных регистров: регистр CS со-
держит начальный адрес сегмента CODE, регистр DS указывает на DATA,
а регистр ES определяет сегмент BUFFER. Утверждение ASSUME
(полагать, считать - прим. перев.) означает именно то, что оно
предписывает ассемблеру. Ассемблер обрабатывает исходный текст
программы предполагая, что сегментные регистры установлены как
указано в этом утверждении. Установка сегментных регистров, сделан-
ная в этом утверждении, остается при ассемблировании в силе пока
другое такое же утверждение не определит новые установки. Ассемблер
обрабатывает эти утверждения последовательно, даже если программа
ветвится и закручивается в циклы. Утверждение ASSUME остается в
силе, пока ассемблер не встретит при последовательном просмотре
программы следующее. Заметим, что в утверждении ASSUME не обязано
определять все сегментные регистры. В нашем примере не объявлен
регистр SS. На практике содержимое сегментного ргистра может быть
временами и неизвестно в программе. В этих случаях утверждение
ASSUME должно указывать сегмент NOTHING. Например, утверждение
ASSUME ES:NOTHING
сообщает ассемблеру, что программа не знает, куда указывает допол-
нительный сегментный регистр. Поскольку значение регистра неизвест-
но, ассемблер не должен использовать его в адресных вычислениях.
Важно отметить, что утверждение ASSUME не генерирует команд ма-
шинного языка. Это директива ассемблеру полагать, что сегментные
регистры установлены в соответствии с указанной в этом утверждении
информацией. Добиться правильное установки сегментов - забота
программиста. Аналогично, ассемблер не может проверить, что утверж-
дение ASSUME при выполнении будет соответствовать содержимому
сегментных регистров. Из-за того, что программа может прийти к
любому конкретному ASSUME множеством разных путей, за его
корректность отвечает программист. В нашем примере предполагается,
что сегментные регистры устанавливаются до выполнения данного кус-
ка программы. Если они установленыы неверно, то программа будет вы-
полняться неправильно даже если ассемблирование прошло успешно.
Первая команда увеличивает значение VAR1, находящейся в сегмен-
те данных. Ассемблер полагает, что регистр DS указывает на сегмент
DATA в соответствии с утверждением ASSUME. Поскольку регистр DS
предполагается при использовании данных по умолчанию, то для этой
команды ассемблер не генерирует сегментный префикс. Сформированная
этой инструкцией 4-байтовая машинная команда не содержит сегментно-
го префикса.
Вторая команда определяет переменную VAR2, которая находится в
сегменте названном BUFFER. Программа сообщила ассемблеру, что
дополнительный сегментный регистр указывает на сегмент BUFFER. Для
увеличения VAR2 ассемблер генерирует четырехбайтовую команду машин-
ного языка, но ей предшествует команда с однобайтовым префиксом,
которая отменяет использование регистра DS в этой команде.
Префиксный байт 26H говорит процессору использовать при создании
20-битового адреса памяти регистр ES. В колонке объектных кодов на
листинге ассемблер отмечает префиксную команду двоеточием.
Третья команда изменяет переменную VAR3 в сегменте CODE.
Утверждение ASSUME связывает этот сегмент с регистром CS. Ассемблер
автоматически генерирует соответствующий префикс переназначения
сегмента. В данном случае префикс 2EH предписывает процессору
использовать при вычислении испольнительного адреса регистр CS.
Вначале утверждение ASSUME покажется излишеством. В первое
время при написании программы естественно забывать о его примене-
нии. Ассемблер выдаст массу сообщений об ошибках чтобы помочь вам в
вашей забывчивости. Но при достаточном опыте, утверждение ASSUME
помогает программисту ассемблера сосредоточиться на структурах дан-
ных в программе. Программист должен не забывать устанавливать
сегментные регистры для адресации требуемых для программы данных.
Ассемблер облегчит бремя запоминания для каждой команды, где
располагаются данные и какой сегментный регистр должен быть
использован чтобы попасть к ним.
Программа может использовать утверждение SEGMENT для передачи
информации другим программам. Оператор SEGMENT может задавать
выравнивание сегмента в памяти, способ его комбинирования с други-
ми сегментами и имя его типа. Для программистов IBM PC особый
интерес представляют два вида выравнивания сегментов. Выравнивание
по параграфам (тип PARA) размещает начало сегмента с начала
параграфа - ячейки памяти, адрес которой в памяти кратен 16-ти .
Это означает, что первый байт сегмента будет иметь смещение 0
относительно значения сегментного регистра. Выравнивание по байтам
(тип BYTE), наоборот, размещает сегмент в любом месте памяти. В
этом случае сегментный регистр может и не указывать на первый байт
сегмента. В программе может потребоваться ненулевое смещение для
доступа к началу сегмента.
Различные способы связывания сегментов задает параметр типа
связи. Особенно это полезно при модульном программировании.
Описание PUBLIC приводит к объединению всех сегментов с одинаковыми
именами в один большой сегмент. Например, можно объединить все
сегменты кодов. Это приведет к соединению разных подпрограмм в их
собственных модулях с главной процедурой. Другой полезный тип связи
- AT, при указании которого в сочетании с адресным выражением,
сегмент располагается по заданному абсолютному адресу. Такое
объявление необходимо при работе с данными в фиксированном месте,
например, с векторами прерываний в начале памяти.
Намного более полное описание описание утверждения SEGMENT
можно найти в справочном томе к макроассемблеру IBM PC. Некоторые
из возможностей опертора SEGMENT мы будем использовать далее в
примерах.