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



  

Раздел для начинающих

В данной главе приведены первые примеры программ, поэтому имеет смысл поговорить о принятом в книге оформлении текстов примеров, об описании используемых в них переменных и о некоторых общих вопросах, связанных с оформлением программ на языке ассемблера. Если вы владеете ассемблером и имеете опыт программирования на этом языке, то данный раздел можно пропустить без ущерба для понимания излагаемого в последующих главах материала. Начинающим рекомендуем обратиться к специальной литературе, а при наличии доступа к Internet посетите сайт www.assembier.ru. Данная книга не является руководством по программированию на языке ассемблера, она содержит лишь минимум сведений, необходимый для понимания действий, выполняемых в примерах.

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

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

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

Для большей наглядности текста отдельные части составных имен могут выделяться заглавными буквами, например oidMode, или отделяться друг от друга нижней чертой, например cur_win. He следует увлекаться слишком длинными именами, поскольку теряется наглядность и повышается вероятность ошибки при их наборе.

Команда — это элементарная единица любой ассемблерной программы, исполняемая процессором ПК. Нас интересует язык ассемблера для микропроцессоров Intel, поскольку на их основе собираются компьютеры семейства IBM PC. В записи на этом языке команда состоит из условного обозначения операции и операндов, количество которых изменяется от 0 до 3 (чаще всего 1 или 2). Операнды отделяются от операции пробелами, а друг от друга запятой и пробелами. Наличие запятой обязательно, а количество пробелов не ограничивается, поэтому вы можете оформлять текст своей программы так, как сочтете нужным.

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

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

Вернемся к командам. Операции имеют зарезервированные имена и их изменения недопустимы. Ассемблер формирует код машинной инструкции исходя из имени операции и результатов анализа операндов. Поэтому одной операции может соответствовать несколько разных кодов машинных инструкций. Например, имя mov обозначает операцию пересылки, которую выполняют 8 разных инструкций микропроцессора Intel 80386.

В качестве операндов могут использоваться имена регистров, констант, переменных, меток и выражения, составленные из перечисленных величин.

Регистры являются внутренними устройствами микропроцессора, т. е. они входят в его состав, поэтому обращение к ним происходит быстрее, чем к оперативной памяти. Начиная с Intel 8086, все модели микропроцессоров содержат 16-разрядные регистры, имеющие следующие имена:

/1/

АХ, ВХ, СХ, DX, DI, SI, ВР, SP, CS, DS, ES, SS.

Первые четыре имени списка /1/ относятся к регистрам общего назначения. При выполнении задач в них обычно находятся сами операнды, а не их адреса. Исключением является регистр вх, в котором может храниться адрес операнда. Каждый из регистров общего назначения делится на два независимых байта. Старшим байтам соответствуют имена АН, вн, сн, он, а младшим байтам — AL, BL, CL, DL.

Имена DI, SI, BP, SP относятся к регистрам-указателям. Обычно они содержат адреса оперативной памяти, в которых хранятся операнды. В таких случаях при записи команды имя регистра заключается в квадратные скобки. Регистры-указатели не делятся на байты, поскольку адрес не может быть 8-разрядным. DI и SI обычно используются при работе с данными, поэтому по умолчанию в качестве сегментного регистра процессор выбирает DS. Специально для работы со стеком предназначены ВР и SP, поэтому по умолчанию в качестве сегментного регистра используется ss.
Существует специальный регистр, содержащий адрес очередной выполняемой команды. В русскоязычной литературе его называют счетчиком команд, а в англоязычной — указателем инструкций (IP). В явном виде он не указывается ни в одной команде, поэтому его имя отсутствует в списке /1/. Тем Не менее, все команды передачи управления изменяют содержимое IP.

Последняя четверка имен списка /1/ соответствует сегментным регистрам. Они предназначены для хранения старшей части адресов операндов или команд. Содержимое сз (сегмент кодов) процессор использует при выборке очередной команды. Содержимое DS (сегмент данных) — при чтении и записи операндов. В ss хранится сегмент оперативной памяти, отведенный для стека. Регистр ES используется строковыми командами при записи результата, в остальных случаях программист может распоряжаться им по своему усмотрению. Во всех примерах, приводимых в данной книге, регистр ES используется при обращениях к видеопамяти, поэтому в нем должно находиться значение сегмента видеопамяти (видеосегмента).

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

Начиная с модели Intel 386, регистры общего назначения и указатели расширены до 32-х разрядов. Размеры сегментных регистров не изменились, но добавились два новых. Новые имена перечислены в списке /2/.

/2/

ЕАХ, ЕВХ, ЕСХ, EDX, EDI, ESI, EBP, ESP, FS, GS.

Расширение регистров и введение новых имен никак рне отразилось на назначении и возможности использования старых. Просто 16-разрядные регистры стали младшими словами 32-разрядных регистров. Однако старшие слова 32-разрядных регистров самостоятельно не существуют и поэтому не имеют собственных имен.

Одновременно с введением новых регистров был расширен набор способов адресации операндов. Адрес может находиться в любом из 32-разрядных регистров, а ЕАХ, ЕВХ, ЕСХ и EDX могут использоваться в индексных выражениях.

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

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

Константы — это постоянные величины, которым присвоены определенные имена. Они описываются (или определяются) с помощью операторов присваивания, имеющих следующую структуру:

<имя константы> = арифметическое или логическое выражение>

Простейший пример такого оператора CR = 0Dh. Если его включить в текст программы, то при компиляции все имена CR будут заменены на код ooh (аббревиатура CR расшифровывается как "возврат каретки" и ей соответствует код 0Dh). В более сложных случаях в выражениях кроме чисел могут использоваться имена констант и переменных, символы, обозначающие арифметические или логические операции, и круглые скобки для указания порядка выполнения вычислений.

При программировании на ассемблере константы могут быть только целыми числами, поэтому операторы типа PI = 3.14 вызовут сообщение об ошибке. Не все операции допускают использование констант, например, константу нельзя записать в сегментный регистр с помощью операции пересылки (mov).

Более общей формой описания констант является использование директивы EQU вместо знака равенства. Если справа (после директивы) указано арифметическое или логическое выражение, то обе директивы (EQU и =) равноценны. Однако после EQU можно записать символьное выражение, которое Макроассемблер будет подставлять вместо имени константы при каждом ее использовании в программе. Кроме того, после EQU можно указать произвольный текст, заключенный в угловые скобки. В таком случае имя константы будет соответствовать указанному тексту (за исключением угловых скобок).

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

Сегментирование

При компиляции программы (при ее преобразовании в объектный код) Макроассемблер преобразует имена переменных в относительные адреса. Следовательно, должна существовать некая точка отсчета, адрес которой можно принять за нуль. Такой точкой является начало сегмента. В памяти ПК сегмент — это произвольно выбранный участок адресов, размер которого не меньше чем 16 и не больше чем 65 536 байтов. Наименьшее значение объясняется принятым на IBM PC способом вычисления полного адреса, а наибольшее соответствует предельному значению числа, которое может быть записано в 16-разрядный регистр.

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

В задачах среднего размера, обычно используется три сегмента для размещения данных, команд и стека. Они ассоциируются с тремя сегментными регистрами DS, CS и SS, используемыми процессором при выполнении команд. Выделение нескольких сегментов в структуре программы преследует две основные цели. Во-первых, ее текст становится более удобочитаемым. Во-вторых — это один из способов увеличения пространства памяти, отведенного для задачи. Вопросы сегментирования программ и распределения памяти обсуждаются в приложении Б данной книги. Здесь мы попробуем разобраться с тем, как устанавливается связь между сегментами программы и соответствующими регистрами процессора.

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

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

; точка входа в задачу, имя start надо указать после последнего end.
start: mov ax, data ; запись значения сегмента в ах
mov ds, ax ; копирование ах в ds
; продолжение программы

Первая команда этого фрагмента записывает значение указанного сегмента в регистр ах, а вторая переписывает содержимое ах в ds. Промежуточный регистр ах нужен потому, что имя сегмента (data) является константой и его нельзя записать в сегментный регистр с помощью операции пересылки. После выполнения этих двух команд процессор "знает", где находятся операнды команд. Таким способом можно записать в любой сегментный регистр значение нужного сегмента.

Вне сегментов, т. е. в начале текста программы располагаются директивы, которые используются только при компиляции. К ним относятся описание констант, о котором говорилось выше, и макроопределения.

Макроопределения и макровызовы объединяет одно общее понятие "Макросы". Это средство для сокращения исходных текстов программ (но не задач), придания ему большей наглядности и упрощения процесса программирования. В комплект поставки Макроассемблера входит несколько специальных файлов, содержащих различные полезные макроопределения. Эти файлы обычно имеют тип (расширение) inc. Если при установке ком-, пилятора создается каталог include, то они располагаются в нем.
В данном разделе автор попытался ответить на часть вопросов, которые могут возникнуть у читателя при изучении приведенных в книге примеров. По мере изложения материала будут описаны особенности выполнения некоторых команд, использованных в примерах. Многие из приведенных в книге примеров оформлены в виде подпрограмм. Поэтому приложение В специально посвящено вопросам, связанным с разработкой и использованием подпрограмм.

  
Назад Начало