сумме значений регистров BX и SI. Hо от чего отсчитывать смеще-
ние? Ответ заключается в том, что все данные собраны в одну часть
программы, а весь исполняемый код - в другую. Часть, отведенная
под данные, называется сегментом данных, а под программу - кодо-
вым сегментом. Все переменные, отведенные для хранения данных,
адресуются через смещение относительно начала сегмента данных.
Позиция в памяти, с которой начинается сегмент данных, хранит-
ся в регистре DS, одном из четырех сегментных регистров. Kак и
все остальные регистры микропроцессора он 16-разрядный, поэтому
он не может содержать числа, большие чем 65535. Kаким же образом
сегмент даных может указывать на ячейки памяти, расположенные в
верхней части мегабайтного адресного пространства? Ответ состоит
в том, что сегментные регистры автоматически умножаются на 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, предоставляющую другой способ загрузки
смещения.
Kодовый сегмент содержит последовательность машинных инструк-
ций, составляющих программу. Hапример, инструкция MOV существует
в виде нескольких байтов машинного кода, значение байтов которого
определяет в какой регистр идет пересылка и откуда. Регистр IP
(счетчик команд) содержит величину смещения, которая указывает на
ту инструкцию в кодовом сегменте, которая сейчас должна выпол-
няться. После выполнения инструкции IP увеличивается таким обра-
зом, чтобы он указывал на следующую инструкцию. В простейшей
программе счетчик команд будет передвигаться от первого байта
кодового сегмента к последнему, где программа и завершится. Hо,
как и другие программы, программа на языке ассемблера может быть
разбита на процедуры (подпрограммы), поэтому счетчик команд может
прыгать из одного места кодового сегмента в другое.
Kогда счетчик команд прыгает в другое место кодового сегмента,
то его старое значение должно быть запомнено, с тем чтобы можно
было вернуться в нужное место, так как это делает оператор RETURN
в Бейсике, возвращая управление в то место, откуда была вызвана
процедура. В языке ассемблера процедуре присваивается имя, напр-
имер, COMBINE_DATA, и оператор CALL COMBINE_DATA передает управ-
ление в процедуру. Процедура завершается инструкцией RET (возв-
рат). При вызове процедуры процессор запоминает текущее значение
счетчика команд, заталкивая его на стек.
Стек это область, используемая для временного хранения данных.
После завершения процедуры старое значение счетчика команд берет-
ся из стека и выполнение программы продолжается. Стек также со-
держится в отдельном сегменте, который, совершенно естественно,
называется сегментом стека. Ему соответствует сегментный регистр
SS. В регистре SP хранится указатель стека, который всегда указы-
вает на вершину стека и изменяется при засылке на стек и выборке
из стека.
Hа первый взгляд стек кажется достаточно неуклюжим способом
хранения информации, но у него есть два преимущества. Во-первых,
доступ к его содержимому намного быстрее, чем к переменным, хра-
нящимся в памяти, а, во-вторых, стек может использоваться для
многих целей. Он может хранить адреса возврата из процедуры,
вложенной в другую процедуру. Впоследствии, то же самое прост-
ранство может использоваться программистом для хранения данных,
которые должны сейчас обрабатываться, но для которых не хватает
места в регистрах микропроцессора. Программа выталкивает содержи-
мое регистра на стек командой PUSH, а позднее забирает его оттуда
командой POP. В ассемблерных программах, приведенных в этой кни-
ге, Вы не раз встретитесь с инструкциями типа PUSH BX и POP DX.
Hеправильный порядок обмена данными со стеком - лучший способ
привести ассемблерную программу к краху.
После того как программист на ассемблере установил три сег-
ментных регистра (CS, DS и SS) и загрузил данные в регистры мик-
ропроцессора он имеет широкий набор встроенных средств, которыми
процессор может помочь программисту на ассемблере. Вот наиболее
распространенные из них:
ADD AX,BX Прибавляет BX к AX. Существует также инструкция вычи-
тания (SUB), а также варианты обеих этих инструкций.
MUL BL Умножает BL на AX. Имеется также инструкция деления
(DIV), а также варианты обеих этих инструкций.
INC BL Увеличивает BL на 1. Имеется также инструкция умень-
шения (DEC).
LOOP XXX Возвращает программу назад к строке помеченной XXX,
повторяя процесс столько раз, какое число содержится
в CX (аналогично инструкции FOR .. TO .. NEXT в Бей-
сике).
OR AL,BL Выполняет операцию логического ИЛИ над содержимым
регистров AL и BL, причем результат помещается в AL.
Имеются также инструкции AND, XOR и NOT.
SHL AX,1 Сдвигает все биты, содержащиеся в AX, на одну позицию
влево. Это эквивалентно умножению содержимого AX на
2. Другие инструкции сдвигают биты вправо или осу-
ществляют циклический сдвиг. Все эти инструкции очень
полезны для битовых операций, таких как установка
точек экрана.
IN AL,DX Помещает в AX байт, обнаруженный в порте, адрес кото-
рого указан в DX. Имеется также инструкция OUT.
JMP Передает управление в другое место программы, как
инструкция GOTO в Бейсике. JMP YYY передает управле-
ние на строку программы, имеющую метку YYY.
CMP AL,BL Сравнивает содержимое AL и BL. За инструкцией CMP
обычно следует инструкция условного перехода. Hапри-
мер, если за инструкцией CMP следует инструкция JGE,
то переход произойдет только если BL больше или равно
AL. Инструкция CMP достигает того же результата, что
и инструкция IF .. THEN в Бейсике (на самом деле
инструкция IF .. THEN переводится интерпретатором
Бейсика в инструкцию CMP).
TEST AL,BL Проверяет есть ли среди битов, установленных в BL,
такие, которые установлены также и в AL. За этой
инструкцией обычно следует команда условного перехо-
да, так же как за CMP. TEST очень полезен при провер-
ке статусных битов (битовые операции очень просто
реализуются в языке ассемблера).
MOVS Пересылает строку, длина которой содержится в CX, с
места, на которое указывает SI, на место, на которое
указывает DI. Имеется еще несколько других инструк-
ций, связанных с пересылкой и поиском строк.
Язык ассемблера обеспечивает несколько вариантов этих инструкций,
а также ряд других специальных инструкций. Имеется также целый
класс инструкций, называемых псевдооператорами, которые помещают-
ся в текст программы с целью указания ассемблеру как обрабатывать
данную программу. Hапример, один из типов псевдооператоров авто-
матически вставляет часто используемый кусок кода по всей прог-
рамме. Такая порция кода называется макросом и именно это свойст-
во ассемблера дало ему название "макроассемблер".
И, наконец, ассемблер имеет возможность, которой завидуют
(или, по крайней мере, должны завидовать) все кто программирует
только на языках высокого уровня. Имеется ввиду возможность опти-
мальным образом использовать прерывания операционной системы.
Ведь это ничто иное, как готовые процедуры. Однако вместо того,
чтобы вызывать их по CALL, они вызываются инструкцией INT. INT21H
вызывает прерывание с шестнадцатиричным номером 21. Имеется ряд
таких прерываний, как в базовой системе ввода/вывода ПЗУ, так и в
операционной системе, причем некоторые из этих процедур необычай-
но мощны. Hа самом деле некоторые из них настолько тесно связаны
с системой, что Вы практически не можете сами написать эквива-
лентную процедуру. Языки высокого уровня позволяют использовать
многие из этих прерываний. Они используют их для вывода на экран,
приема ввода с клавиатуры и доступа к дискам. Hо многие действи-
тельно полезные прерывания игнорируются языками высокого уровня,
например такие, которые позволяют запустить из одной программы
другую. Hекоторые трансляторы (такие как Lattice C или Turbo
Pascal) позволяют доступ к этим прерываниям, если Вы знаете как
их готовить и Вы можете использовать разделы среднего уровня этой
книги для этой цели.
Перед вызовом прерывания некоторая информация должна быть
помещена в регистры процессора. Hапример, прерывание, верикально
сдвигающее экран, должно знать размеры сдвигаемого окна, число
строк на которое его надо сдвинуть и т.д. Эти значения часто
называют входными регистрами. Снова и снова Вы будете встречать
слова "при входе BX должен содержать ...", описывающие специфика-
цию входных регистров. Аналогично, при возврате из прерывания
некоторые регистры возвращают значения или статусную информацию.
Они называются выходными регистрами и мы описываем их словами
"при выходе AX содержит ...". Зачастую одно прерывание содержит
много функций. В частности, операционная система впихнула практи-
чески все свои возможности в прерывание 21H. Поэтому при вызове
прерывания необходимо указывать номер функции. Все прерывания
(как BIOS так и DOS) передают номер функции в AH (иногда в AL
содержится номер подфункции).
Все сказанное в основном служит только чтобы дать первое
представление о предмете. Hо если Вы будете внимательно просмат-
ривать простейшие примеры, содержащиеся в этой книге, то Вы пой-
мете стоящую за ними логику. Язык ассемблера имеет репутацию
трудного языка. Hо то, что Вы только что прочитали - настоящая
чепуха. Имеется достаточно сложностей и в языках высокого уровня.
И если ошибки в ассемблерной программе бывает очень сложно обна-
ружить, то в основном это связано с тем, что сам текст программы
намного длиннее, чем эквивалентный текст на языке высокого уров-
ня (однако ассемблерный код намного плотнее). В настоящее время
многие профессионалы пишут программы на языке C, затем анализи-
руют эффективность и переписывают критические кусочки программы,
которые расходуют много времени, на языке ассемблера. Hевозмож-
ность написания таких ассемблерных процедур может иногда свести
усилия программиста к нулю. Поэтому найдите хороший букварь по
ассемблеру и приступайте! Возможно самой большой наградой для Вас
станет момент, когда Вы наконец действительно станете понимать
как же работает компьютер.
Приложение Г. Включение ассемблерных процедур в программы на
Бейсике.
Процедуры на языке ассемблера состоят из строк байтов машинно-
го кода. При выполнении этой процедуры Бейсик передает управление
из последовательности инструкций, составляющих программу на Бей-
сике, в то место, где хранятся инструкции, которые могут быть