Справочник программиста на персональном компьютере фирмы IBM. Приложения - Основные сведения о языке ассемблера
ОГЛАВЛЕНИЕ
Приложение В. Основные сведения о языке ассемблера.
Читатель этой книги, не знакомый с языком ассемблера, скоро
поймет, что многие программистские трюки не могут быть достигнуты
другими средствами. Хотя изучение языка ассемблера требует от-
дельной книги, в этом приложении приводятся основные понятия,
которые помогут новичкам разобраться в примерах на этом языке.
Внимательный просмотр разделов, посвященных среднему и низкому
уровням, даст Вам возможность получить представление о том, как
работает ассемблер, после чего намного легче изучить разные част-
ные вопросы. Здесь обсуждаются не все ассемблерные инструкции,
встречающиеся в программах, но Вы обнаружите, что около 95 %
инструкций, встреченных Вами в программах, описаны здесь, а зна-
чение остальных может быть понято благодаря комментариям к прог-
раммам.
Микропроцессор 8088 имеет 13 16-разрядных регистров, каждый из
которых имеет свои функции. В то время как в языках высокого
уровня Вы можете поместить два числа в переменные, а затем сло-
жить эти переменные, то в языке ассемблера эти числа помещаются в
регистры микропроцессора, а затем складываются значения, содержа-
щиеся в регистрах. Все операции в языке ассемблера состоят в
обмене данных с регистрами, а затем выполнении операций на ре-
гистрах, таких как изменение отдельных битов, выполнение арифме-
тических операций и т.д. Одной из причин высокой эффективности
языка ассемблера является хранение данных в регистрах микропро-
цессора; компиляторы имеют тенденцию возвращать все значения в
память после выполнения операции, а доступ к памяти требует боль-
шого времени. На рис. В-1 показаны 13 регистров микропроцессоров
8088 и 80286 (последний имеет дополнительные средства для много-
задачной работы, которые мы не будем рассматривать здесь).
Регистры AX, BX, CX и DX являются регистрами общего назначе-
ния. Их особенность состоит в том, что операции могут произво-
диться не только над содержимым всего регистра, но также и над
половиной. Каждый из четырех регистров делится на старшую и млад-
шую части, например, AH обозначает старшую половину регистра AX,
а AL - младшую. Точно так же ассемблерная программа может иметь
доступ к BH, BL, CH, CL, DH и DL. Это свойство очень полезно,
поскольку часто программе приходится работать с байтными величи-
нами. Регистры BP, SI и DI также достаточно удобны, хотя они
могут принимать только 16-битные значения. Каждый бит регистра
флагов сообщает о соответствующем статусе процессора, например, о
том, что при выполнении арифметической операции был перенос за
разрядную сетку.
В общем случае значения помещаются в регистры с помощью инст-
рукции MOV. MOV AX,BX пересылает содержимое регистра BX в AX,
затирая ранее содержащееся в AX значение. MOV AH,BL приводит к
пересылке байта из регистра в регистр, но MOV AX,BL - недопусти-
мая инструкция, так как значения должны иметь одинаковый размер.
Инструкция MOV можеть также передавать значения из памяти, напри-
мер, MOV AX,ACCT_NUMBER. Здесь ACCT_NUMBER - имя переменной,
которую создал программист, совсем как в языке высокого уровня.
Переменная создается оператором вида ACCT_NUMBER DW 0. Этот опе-
ратор оставляет место для слова (двух байтов), присваивая им
значение 0. Другие допустимые символы в этом операторе это DD -
для двойного слова и DB - для байта или строк. Ассемблер следит
за адресами переменных, поэтому при ассемблировании оператора MOV
AX,ACCT_NUMBER имя переменной заменяется на ее адрес.
Работа с именами переменных - самый простой способ идентифика-
ции данных в программах на языке ассемблера. Но имеются различные
способы хитрой адресации, которые позволяют программе хранить
массивы или использовать указатели. Например, MOV AX,[BX][SI]
посылает в AX значение, которое содержится по смещению, равному
сумме значений регистров BX и SI. Но от чего отсчитывать смеще-
ние? Ответ заключается в том, что все данные собраны в одну часть
программы, а весь исполняемый код - в другую. Часть, отведенная
под данные, называется сегментом данных, а под программу - кодо-
вым сегментом. Все переменные, отведенные для хранения данных,
адресуются через смещение относительно начала сегмента данных.
Позиция в памяти, с которой начинается сегмент данных, хранит-
ся в регистре DS, одном из четырех сегментных регистров. Как и
все остальные регистры микропроцессора он 16-разрядный, поэтому
он не может содержать числа, большие чем 65535. Каким же образом
сегмент даных может указывать на ячейки памяти, расположенные в
верхней части мегабайтного адресного пространства? Ответ состоит
в том, что сегментные регистры автоматически умножаются на 16, а
результат указывает на место в памяти, с которого начинается
сегмент. Таким образом, сегменты всегда выравнены на 16-байтную
границу. После того как сегмент установлен, все остальные регист-
ры могут содержать смещения, указывающие на любой из следующих
65535 байтов. Регистр дополнительного сегмента (ES) также исполь-
зуется для указания на данные, хранящиеся в памяти.
Среди ассемблерных инструкций, которые Вы часто будете встре-
чать в этой книге, есть инструкции загрузки сегментных и относи-
тельных адресов переменных. MOV AX,SEG ACCT_NUMBER помещает зна-
чение сегментного регистра, в котором расположен ACCT_NUMBER в
AX, а впоследствии это значение будет переслано в DS. MOV BX,OFF-
SET ACCT_NUMBER помещает в BX смещение переменной ACCT_NUMBER в
сегменте данных. После выполнения этих операций DS:BX будут ука-
зывать на ACCT_NUMBER. Если ACCT_NUMBER является одномерным мас-
сивом, то для указания на определенный элемент массива может
использоваться добавочное смещение. Вы часто будете встречать
также инструкцию LEA, предоставляющую другой способ загрузки
смещения.
Кодовый сегмент содержит последовательность машинных инструк-
ций, составляющих программу. Например, инструкция MOV существует
в виде нескольких байтов машинного кода, значение байтов которого
определяет в какой регистр идет пересылка и откуда. Регистр IP
(счетчик команд) содержит величину смещения, которая указывает на
ту инструкцию в кодовом сегменте, которая сейчас должна выпол-
няться. После выполнения инструкции IP увеличивается таким обра-
зом, чтобы он указывал на следующую инструкцию. В простейшей
программе счетчик команд будет передвигаться от первого байта
кодового сегмента к последнему, где программа и завершится. Но,
как и другие программы, программа на языке ассемблера может быть
разбита на процедуры (подпрограммы), поэтому счетчик команд может
прыгать из одного места кодового сегмента в другое.
Когда счетчик команд прыгает в другое место кодового сегмента,
то его старое значение должно быть запомнено, с тем чтобы можно
было вернуться в нужное место, так как это делает оператор RETURN
в Бейсике, возвращая управление в то место, откуда была вызвана
процедура. В языке ассемблера процедуре присваивается имя, напр-
имер, COMBINE_DATA, и оператор CALL COMBINE_DATA передает управ-
ление в процедуру. Процедура завершается инструкцией RET (возв-
рат). При вызове процедуры процессор запоминает текущее значение
счетчика команд, заталкивая его на стек.
Стек это область, используемая для временного хранения данных.
После завершения процедуры старое значение счетчика команд берет-
ся из стека и выполнение программы продолжается. Стек также со-
держится в отдельном сегменте, который, совершенно естественно,
называется сегментом стека. Ему соответствует сегментный регистр
SS. В регистре SP хранится указатель стека, который всегда указы-
вает на вершину стека и изменяется при засылке на стек и выборке
из стека.