Сегментная структура программ
Как было показано выше, обращение к памяти осуществляется исключительно посредством сегментов – логических образований, накладываемых на любые участки физического адресного пространства. Начальный адрес сегмента, деленный на 16, т.е. без младшей шестнадцатеричной цифры, заносится в один из сегментных регистров; после этого мы получаем доступ к участку памяти, начинающегося с заданного сегментного адреса.
Каким образом понятие сегментов памяти отражается на структуре программы? Следует заметить, что структура программы определяется, с одной стороны, архитектурой процессора (если обращение к памяти возможно только с помощью сегментов, то и программа, видимо, должна состоять из сегментов), а с другой – особенностями той операционной системы, под управлением которой эта программа будет выполняться. Наконец, на структуру программы влияют также и правила работы выбранного транслятора – разные трансляторы предъявляют несколько различающиеся требования к исходному тексту программы.
При подготовке этой книги для трансляции и отладки примеров программ использовался пакет TASM 5.0 корпорации Borland International; он удобен, в частности, наличием наглядного многооконного отладчика. Вопрос этот, однако, не принципиален, и читатель может для отладки примеров, приведенных в книге, воспользоваться любым ассемблером, ознакомившись предварительно с его описанием.
В настоящем разделе мы на простом примере рассмотрим особенности сегментной адресации и роль регистров процессора в выполнении прикладной программы. Однако для того, чтобы программа была работоспособна, нам придется включить в нее ряд элементов, не имеющих прямого отношения к рассматриваемым вопросам, но необходимых для ее правильного функционирования. К таким элементам, в частности, относится вызов функций DOS. Приведя полный текст программы, мы дадим краткие пояснения.
Пример 1.1. Простая программа с тремя сегментами.
;Укажем соответствие сегментных регистров сегментам assume CS:code,DS:data ;Опишем сегмент команд code segment;Откроем сегмент команд begin: mov AX,data;Настроим DS mov DS,AX;на сегмент данных; Выведем на экран строку текста mov АН,09h;Функция DOS вывода на экран mov DX,offset msg;Адрес выводимой строки int 21h;Вызов DOS ;Завершим программу mov AX,4C00h;Функция DOS завершения программы int 21h;Вызов DOS code ends;Закроем сегмент команд ;Опишем сегмент данных data segment;Откроем сегмент данных msg db "Программа работает!$';Выводимая строка data ends;Закроем сегмент данных ;Опишем сегмент стека stk segment stack;Откроем сегмент стека db 256 dup (?);Отводим под стек 256 байт stk ends;Закроем сегмент стека end begin;Конец текста с точкой входа
Следует заметить, что при вводе исходного текста программы с клавиатуры можно использовать как прописные, так и строчные буквы; транслятор воспринимает, например, строки MOV AX,DATA и mov ax,data одинаково. Однако с помощью соответствующих ключей можно заставить транслятор различать прописные и строчные буквы в отдельных элементах предложений. В настоящей книге в текстах программ и при описании операторов языка в основном используются строчные буквы, за исключением обозначений регистров, которые для наглядности выделены прописными буквами.
Предложения языка ассемблера могут содержать комментарии, которые отделяются от предложения языка знаком точки с запятой (;). При необходимости комментарий может занимать целую строку (тоже, естественно, начинающуюся со знака ";"). Поскольку в языке ассемблера нет знака завершения комментария, комментарий нельзя вставлять внутрь предложения языка, как это допустимо делать во многих языках высокого уровня. Каждое предложение языка ассемблера, даже самое короткое, должно занимать отдельную строку текста.
В Программе 1.1 описаны три сегмента: сегмент команд с именем code, сегмент данных с именем data и сегмент стека с именем stk. Описание каждого сегмента начинается с ключевого слова segment, предваряемого некоторым именем, и заканчивается ключевым словом end, перед которым указывается то же имя, чтобы транслятор знал, какой именно сегмент мы хотим закончить. Имена сегментов выбираются вполне произвольно. Текст программы заканчивается директивой ассемблера end, завершающей трансляцию. В качества операнда этой директивы указывается точка входа в программу; в нашем случае это метка begin.
Порядок описания сегментов в программе, как правило, не имеет значения. Часто программу начинают с сегмента данных, это несколько облегчает чтение программы, и в некоторых случаях устраняет возможные неоднозначности в интерпретации команд, ссылающиеся на данные, которые еще не описаны. Мы в начале программы расположили сегмент команд, за ним – сегмент данных и в конце – сегмент стека; такой порядок предоставляет некоторые удобства при отладке программы. Важно только понимать, что в оперативную память компьютера сегменты попадут в том же порядке, в каком они описаны в программе (если специальными средствами ассемблера не задать иной порядок загрузки сегментов в память).