Директивы .STACK, .CODE и .DATA


Директивы определения сегментов .STACK, .CODE и .DATA определяют, соответственно, сегмент стека, сегмент кода и сегмент данных. Например, директива:

.STACK 200h

определяет стек размером в 200h (512) байт. Что касается стека, то это все, что вы сможете сделать. Необходимо просто убедиться, что в вашей программе имеется директива .STACK, и Турбо Ассемблер выделит для вас стек. Для обычных программ вполне подходит стек размером 200h, хотя в программах, интенсивно использующих стек (например, в программах, содержащих рекурсивные вызовы) может потребоваться стек большего размера. (Информация об исключениях при использовании директивы .STACK приведена в разделе «Невыделе-ние стека или выделение слишком маленького стека» в Главе 6.)

Директива .CODE отмечает начало сегмента кода. Вы можете посчитать, что для Турбо Ассемблера достаточно очевидно, что все ваши инструкции относятся к сегменту кода. На самом деле Турбо Ассемблер позволяет вам (с помощью стандартных директив определе-ния сегментов) использовать несколько сегментов кода, а директива .CODE указывает Турбо Ассемблеру, в какой именно сегмент надо по-местить ваши инструкции. Определение сегмента кода еще проще, чем определение сегмента стека, так как аргументы для директивы .CODE указывать не требуется. Например:

.CODE

sub ax,ax ; установить аккумулятор в значение 0

mov cx,100 ; число выполняемых циклов

Директива .DATA несколько более сложна. Как можно понять, директива .DATA отмечает начало сегмента данных. В этом сегменте следует размещать ваши переменные памяти. Например:

.DATA

TopBoundary DW 100

Counter DW ?

ErrorMessage BD 0dh,0dh,’***Ошибка***’,0dh,0ah,’$’

Это довольно просто. Вся «сложность» директивы .DATA заклю-чается в том, что до того, как вы будете обращаться к ячейкам па-мяти в сегменте, определенном с помощью директивы .DATA, нужно явно загружать сегментный регистр DS идентификатором @data. Так как сегментный регистр можно загрузить из регистра общего назна-чения или ячейки памяти, но в него нельзя загрузить константу, регистр DS обычно загружается с помощью последовательности из двух инструкций:

mov ax,@data

mov ds,ax

(Вместо регистра AX можно использовать любой общий регистр.) Дан-ная последовательность инструкций устанавливает DS таким образом, чтобы он указывал на сегмент данных, который начинается по дирек-тиве .DATA.

Следующая программа выводит на экран текст, хранящийся в строке DataString:

DOSSEG

.MODEL

.STACK small

.DATA

DataString DB ‘Этот текст находится в сегменте данных’

.CODE ProgramStart:

mov bx,@data

mov ds,bx ; устанавливает регистр DS на сегмент

; данных

mov dx,OFFSET DataString ; DX указывает на смещение

; DataString в сегменте .DATA

mov ah,9 ; номер функции DOS печати строки

int 21h ; вызвать DOS для печати строки

mov ah,4ch ; вызвать DOS для завершения программы

END ProgramStart

Без двух инструкций, которые устанавливают регистр DS в зна-чение сегмента, определенного с помощью директивы .DATA, функция печати строки не будет правильно работать. Строка DataString на-ходится в сегменте данных и недоступна, пока регистр DS не будет установлен в значение этого сегмента. Это можно рассматривать следующим образом: когда вы вызываете операционную систему DOS для печати строки, в паре регистров DS:DX вы передаете полный ад-рес в формате «сегмент:смещение». Полный указатель вида «сегмент: смещение» вы получите только после того, как в регистр DS будет загружен сегмент данных, а в DX — смещение DataString.

У вас может возникнуть вопрос, почему нужно загружать ре-гистр DS, а не CS или SS (или ES)?

Во первых, регистр CS никогда не загружается явно, так как при запуске программы за вас это делает операционная система DOS. Кроме того, если бы регистр CS уже не был установлен, когда приш-ло время выполнить первую инструкцию программы, процессор 8086 не знал бы, где найти эту инструкцию, и программа не стала бы рабо-тать. Пока для вас это может быть недостаточно очевидно, но по-верьте нам на слово: регистр CS загружается при запуске программы автоматически, и у вас нет необходимости загружать его явно.

Аналогично, при запуске программы DOS загружает регистр SS, значение которого при выполнении программы обычно остается неиз-менным. Хотя изменять содержимое регистра SS можно, это редко оказывается желательным, и определенно это не стоит делать, если вы не знаете точно, что вы делаете. Таким образом, регистр SS аналогично регистру CS устанавливается при начале выполнения программы автоматически, и далее его трогать не нужно.

Регистр DS существенно от них отличается. В то время как ре-гистр CS указывает на инструкции, а SS — на стек, регистр DS ука-зывает на данные. Программы не работают непосредственно с инс-трукциями или стеком, однако с данными они работают постоянно. Кроме того, программы могут в любой момент пожелать получить дан-ные в нескольких разных сегментах. Нужно помнить о том, что про-цессор 8086 в любое время позволяет вам получить доступ к любой ячейке памяти в пределах 1 Мбайта, но только в блоках по 64К (от-носительно сегментного регистра).

Вы можете захотеть загрузить регистр DS одним сегментом, по-лучить доступ к данным в этом сегменте, а затем загрузить DS дру-гим сегментом, чтобы обратиться к другому блоку данных. В малень-ких и средних программах (таких, как в приведенных нами примерах) вам не потребуется использовать более одного сегмента данных, но в больших программах несколько сегментов данных используется час-то. Кроме того, вам потребуется загружать регистр DS различным значениями, если вы хотите получить доступ к системным областям памяти, например, к ячейкам памяти, используемым базовой системой ввода-вывода (BIOS).

Из всего этого можно сделать следующий краткий вывод: Турбо Ассемблер позволяет вам в любой момент установить регистр DS в значение любого сегмента. За эту гибкость приходится расплачи-ваться тем, что вы должны явно устанавливать регистр DS в значе-ние нужного сегмента (обычно @data), что эквивалентно сегменту, который начинается с директивы .DATA. После этого вы сможете по-лучить доступ к ячейкам памяти этого сегмента.

Сегментный регистр ES загружается аналогично регистру DS. Чаще всего вам не потребуется использовать регистр ES, но когда появится необходимость получить доступ к ячейке памяти в сегмен-те, на который указывает регистр ES, вы должны сначала загрузить регистр ES значением этого сегмента. Например, следующая прог-рамма загружает регистр ES значением сегмента .DATA, а затем загружает через ES символ, который нужно напечатать из этого сегмента:

DOSSEG

.MODEL small

.STACK 200h

.DATA

OutputChar DB ‘B’

.CODE ProgramStart:

mov dx,@data

mov es,dx ; установить ES в значение

; сегмента .DATA

mov bx,OFFSET OutputChar ; BX указывает на

; смещение OutputChar

mov al,es:[bx] ; получить выводимый символ

; из сегмента, на который

; указывает регистр ES

mov ah,2 ; функция DOS вывода символа

int 21h ; вызвать DOS для вывода

; символа на экран

END ProgramStart

Обратите внимание, что регистр ES (как и регистр DS ранее) загружается последовательностью из двух инструкций:

mov dx,@Data

mov es,dx

Положим, в данном примере нет конкретной причины использо-вать вместо DS регистр ES. Фактически, использование регистра ES означает, что мы применили префикс переопределения сегмента ES:

(как это описывается в Главе 9). Однако во многих случаях чрезвы-чайно удобно, когда регистр DS указывает на один сегмент, а ре-гистр ES — на другой (особенно это касается использования строко-вых инструкций).

Директива DOSSEG

Директива DOSSEG приводит к тому, что сегменты в программе Ассемблера будут сгруппированы в соответствии с соглашениями по упорядочиванию сегментов фирмы Microsoft. В данным момент вам не следует вникать в смысл того, что это означает. Запомните просто, что почти все автономные программы на Ассемблере будут прекрасно работать, если вы начнете их с директивы DOSSEG.

При компоновке модулей Ассемблера с модулями языков высокого уровня задавать директиву DOSSEG не обязательно, так как в языке высокого уровня автоматически выбирается упорядочивание сегментов по стандарту фирмы Microsoft. Однако эта директива не повредит.

Из всего перечисленного можно сделать заключение, что ис-пользование директивы DOSSEG в качестве первой строки вашей прог-раммы является простейшим подходом (если у вас нет конкретной причины поступать иначе). Благодаря этому вы сможете использовать определенный порядок сегментов (более подробно о директиве DOSSEG рассказывается в Главе 3 «Справочного руководства»).