Рассмотрим описание данных в программе на ассемблере. Любая программа предназначена для обработки некоторой информации, поэтому вопрос о том, как описать данные с использованием средств языка, обычно встает одним из первых. TASM предоставляет очень широкий набор средств описания и обработки данных, который вполне сравним с аналогичными средствами некоторых языков высокого уровня.
Начнем обсуждение с правил описания простых типов данных, которые являются базовыми для описания более сложных типов. Для описания простых типов данных в программе используются специальные директивы резервирования и инициализации данных, которые по сути являются указаниями транслятору на выделение определенного объема памяти. Если проводить аналогию с языками высокого уровня, то директивы резервирования и инициализации данных являются определениями переменных. Машинного эквивалента этим директивам нет; просто транслятор, обрабатывая каждую такую директиву, выделяет необходимое количество байт памяти и при необходимости инициализирует эту область некоторым значением. Директивы резервирования и инициализации данных простых типов имеют формат, показанный на рис. 5.17.
На рис. 5.17 использованы следующие обозначения.
§ ? показывает, что содержимое поля не определено, то есть при задании директивы с таким значением выражения содержимое выделенного участка физической памяти изменяться не будет. Фактически создается неинициализированная переменная;
§ значение инициализации — значение элемента данных, которое будет занесено в память после загрузки программы. Фактически создается инициализированная переменная, в качестве которой могут выступать константы, строки символов, константные и адресные выражения в зависимости от типа данных. Подробная информация приведена в приложении ;
§ выражение — итеративная конструкция с синтаксисом, описанным на рис. 5.17. Эта конструкция позволяет повторить последовательное занесение в физическую память выражения в скобках п раз.
§ имя — некоторое символическое имя метки или ячейки памяти в сегменте данных, используемое в программе.
Рис. 5.17. Директивы описания данных простых типов
На рис. 5.17 представлены следующие поддерживаемые TASM директивы ре-зервирования и инициализации данных:
§ db — резервирование памяти для данных размером 1 байт;
§ dw — резервирование памяти для данных размером 2 байта;
§ dd — резервирование памяти для данных размером 4 байта;
§ df — резервирование памяти для данных размером 6 байт;
§ dp — резервирование памяти для данных размером 6 байт;
§ dq — резервирование памяти для данных размером 8 байт;
§ dt — резервирование памяти для данных размером 10 байт.
Очень важно уяснить себе порядок размещения данных в памяти. Он напрямую связан с логикой работы микропроцессора с данными. Микропроцессоры Intel требуют следования данных в памяти по принципу: младший байт по младшему адресу.
Для иллюстрации данного принципа рассмотрим листинг 5.2, в котором определим сегмент данных. В этом сегменте данных приведено несколько директив описания простых типов данных. Используя последовательность шагов, описанную ранее, получим загрузочный модуль.
Посмотрим, как выглядит сегмент данных программы листинга 5.2 в памяти компьютера. Это даст нам возможность обсудить практическую реализацию обозначенного нами принципа размещения данных. Для этого запустим отладчик TD. EXE, входящий в комплект поставки TASM. Опишем этот процесс по шагам.
Введем код из листинга 5.2 в файл с названием prg_5_2.asm. Как мы договорились раньше, все манипуляции с файлом будем производить в директории work, где уже содержатся все необходимые для компиляции, компоновки и отладки файлы пакета TASM.
Запустим процесс трансляции файла следующей командой:
tasm.exe /zi prg_5_2.asm , , ,
После устранения синтаксических ошибок запустим процесс компоновки объектного файла:
tlink.exe /v prg_5_2.obj
Теперь можно производить отладку:
td prg_5_2.exe
Если все было сделано правильно, то в отладчике откроется окно Module с исходным текстом программы. Для того чтобы с помощью отладчика просмотреть область памяти, содержащую наш сегмент данных, необходимо открыть окно Dump. Это делается с помощью команды главного меню View| Dump.
Но одного открытия окна недостаточно; нужно еще настроить его на адрес начала сегмента данных. Этот адрес должен содержаться в сегментном регистре ds, но, как сказано выше, перед началом выполнения программы адрес в ds не соответствует началу сегмента данных. Нужно перед первым обращением к любому символическому имени произвести загрузку действительного физического адреса сегмента данных. Обычно это действие не откладывают в долгий ящик и производят первыми двумя командами в сегменте кода. Действительный физический адрес сегмента данных извлекают как значение предопределенной переменной @data. В нашей программе эти действия выполняют команды
mov ах,@data
mov ds,ах
Для того чтобы посмотреть содержимое нашего сегмента данных, нужно остановить выполнение программы после этих двух команд. Это можно сделать, если перевести отладчик в пошаговый режим с помощью клавиш F7 или F8. Нажмите два раза F8. Теперь можно открыть окно Dump.
В окне Dump вызовите контекстное меню, щелкнув правой кнопкой мыши. В появившемся контекстном меню выберите команду Goto. Появится диалоговое окно, в котором нужно ввести начальный адрес памяти, начиная с которого будет выводиться информация в окне Dump. Синтаксис задания этого адреса должен соответствовать синтаксису задания адресного операнда в программе на ассемблере (см. начало этого урока). Мы бы хотели увидеть содержимое памяти для нашего сегмента данных, начиная с его начала, поэтому введем ds: 0000. Для удобства, если сегмент достаточно велик, это окно можно распахнуть на весь экран. Для этого нужно щелкнуть на символе в правом верхнем углу окна Dump. Вид экрана показан на рис. 5.18.
Обсудим рис. 5.18. На нем можно видеть данные нашего сегмента в двух представлениях: шестнадцатеричном и символьном. Видно, что со смещением 0000 расположены символы, входящие в строку message. Она занимает 34 байта. После нее следует байт, имеющий в сегменте данных символическое имя perem_1, содержимое этого байта 0ffh. Теперь обратите внимание на то, как размещены в памяти байты, входящие в слово, обозначенное символическим именем реrem_2.
Сначала следует байт со значением 7fh, а затем со значением 3ah. Как видите, в памяти действительно сначала расположен младший байт значения, а затем старший. Теперь посмотрим и самостоятельно проанализируем размещение байтов для поля, обозначенного символическим именем perem_3. Оставшуюся часть сегмента данных можем теперь проанализировать самостоятельно. Остановимся лишь на двух специфических особенностях использования директив резервирования и инициализации памяти. Речь идет о случае использования в поле операндов директив dw и dd символического имени из поля имя этой или другой директивы резервирования и инициализации памяти. В нашем примере сегмента данных это директивы с именами adr и adr_full. Когда транслятор встречает директивы описания памяти с подобными операндами, то он формирует в памяти значения адресов тех переменных, чьи имена были указаны в качестве операндов. В зависимости от директивы, применяемой для получения такого адреса, формируется либо полный адрес (директива dd) в виде двух байтов сегментного адреса и двух байтов смещения, либо только смещение (директива dw). Найдите в дампе на рис. 5.18 поля, соответствующие именам adr и adr_full, и проанализируйте их содержимое.
Puc. 5.18. Окно дампа памяти
Любой переменной, объявленной с помощью директив описания простых типов данных, ассемблер присваивает три атрибута:
1. Сегмент (seg) — адрес начала сегмента, содержащего переменную.
2. Смещение (offset) в байтах от начала сегмента с переменной.
3. Тип (type) — определяет количество памяти, выделяемой переменной в соответствии с директивой объявления переменной.
Получить и использовать значение этих атрибутов в программе можно с помощью рассмотренных нами выше операторов ассемблера seg, offset и type.
Подведем некоторые итоги:
u Программа на ассемблере, отражая особенности архитектуры микропроцессора, состоит из сегментов — блоков памяти, допускающих независимую адресацию.
u Каждый сегмент может состоять из предложений языка ассемблера четырех типов: команд ассемблера, макрокоманд, директив ассемблера и строк комментариев.
u Ассемблер допускает большое разнообразие типов операндов, которые могут содержаться непосредственно в команде, в регистрах и в памяти.
u Операнды в команде могут быть выражениями.
u Исходный текст программы разбивается на сегменты с помощью директив сегментации, которые делятся на стандартные, поддерживаемые всеми транс-ляторами ассемблера, и упрощенные, поддерживаемые транслятором TASM.
u Упрощенные директивы сегментации позволяют унифицировать интерфейс с языками высокого уровня и облегчают разработку программ, повышая наглядность кода.
u Существует два режима работы TASM: MASM и IDEAL. Назначение режима MASM — обеспечить полную совместимость с транслятором MASM фирмы Microsoft. Назначение режима IDEAL — упростить синтаксис конструкций языка, повысить эффективность и скорость работы транслятора.
u TASM поддерживает разнообразные типы данных, которые делятся на простые (базовые) и сложные. Простые типы служат как бы «кирпичиками» для построения сложных типов данных.
u Директивы описания простых типов данных позволяют зарезервировать и при необходимости инициализировать области памяти заданной длины.
u Каждой переменной, объявленной с помощью директивы описания данных, TASM назначает атрибуты, доступ к которым можно получить с помощью соответствующих операторов ассемблера.