Страница 5 из 21
Приложение В. Основные сведения о языке ассемблера. Читатель этой книги, не знакомый с языком ассемблера, скоро поймет, что многие программистские трюки не могут быть достигнуты другими средствами. Хотя изучение языка ассемблера требует от- дельной книги, в этом приложении приводятся основные понятия, которые помогут новичкам разобраться в примерах на этом языке. Внимательный просмотр разделов, посвященных среднему и низкому уровням, даст Вам возможность получить представление о том, как работает ассемблер, после чего намного легче изучить разные част- ные вопросы. Здесь обсуждаются не все ассемблерные инструкции, встречающиеся в программах, но Вы обнаружите, что около 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 хранится указатель стека, который всегда указы- вает на вершину стека и изменяется при засылке на стек и выборке из стека.
|