Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2591

.pdf
Скачиваний:
2
Добавлен:
07.01.2021
Размер:
25.15 Mб
Скачать

. . .

а в другом модуле содержится:

. . .

public FarProc FarProc proc far

. . .

ret FarProc endp

. . .

то ассемблер в соответствии с типом данных, заданным в директиве extrn, сгенерирует ближнее обращение к процедуре FarProc. Можно с определенностью сказать, что такая программа корректно работать не будет, поскольку FarProc на самом деле является процедурой с дальним типом обращения и завершается соответствующей инструкцией ret.

Как уже описывалось, ассемблер обычно (по умолчанию) не различает верхний и нижний регистры букв, поэтому общедоступные метки преобразуются в верхний регистр. Это означает, что в обычном состоянии внешние метки будут интерпретироваться в верхнем регистре. Если требуется, чтобы во внешних метках регистры букв различались, используйте параметры командной строки /ml или /mx.

Для каждого идентификатора в директиве extrn можно также задать язык: C, PASCAL, BASIC, FORTRAN, PROLOG или notlanguage

последнем случае язык не используется). Это приводит к тому, что перед тем, как имя станет в объектном файле общедоступным, к нему автоматически применяются правила указанного языка. Например, если вы описали

extrn C myprog:near

то идентификатор myprog в исходном файле преобразуется во внешний идентификатор _myprog. Использование спецификатора языка в директиве extrn временно отменяет текущее задание языка.

4.4. Директива global

Зачем для обеспечения совместного использования меток разными модулями нужны две директивы – public и extrn? Единственная причина заключается в необходимости обеспечить совместимость с более ранними ассемблерами. Директива global делает все то, что делают директивы public и extrn.

Если с помощью данной директивы вы объявите метку глобальной, а затем определите ее (с помощью директив db, dw, proc, label или других подобных директив), то метка станет доступной другим модулям

21

аналогично тому, как если бы вы вместо директивы global использовали директиву public. Если же вы, с другой стороны, объявляете метку глобально, а затем используете ее без определения, то эта метка интерпретируется как внешняя метка, аналогично тому, как если бы вы объявили ее с помощью директивы extrn. Рассмотрим, например, следующий фрагмент:

...

 

.data

 

global

FinalCount:word,PromptString:byte

FinalCount

dw ?

...

 

.code

 

global

DoReport:near,TallyUp:far

TallyUp

proc far

...

 

call DoReport

...

Здесь метки FinalCount и TallyUp определены, поэтому они становятся общедоступными (для других модулей) метками (public). Метки PromptString и DoReport не определены, поэтому подразумевается, что это внешние (external) метки, которые объявлены общедоступными в других модулях.

Директиву global очень удобно использовать, например, во включаемых файлах (эти файлы мы обсудим в следующем подразделе). Предположим, у вас есть множество меток, которые вы хотите сделать доступными в программе (состоящей из нескольких модулей) для других модулей. Неплохо было бы, если бы вы смогли определить все эти метки во включаемом файле, а затем включить этот файл в каждый модуль. К сожалению, с помощью директив public и extrn это невозможно, так как директива extrn не будет работать в том модуле, в котором определена данная метка, а директива public будет работать только в том модуле, в котором данная метка определена.

Однако директива global допустима во всех модулях, поэтому вы можете сформировать включаемый файл, где все нужные метки объявлены глобальными, а затем включить данный файл во все ваши модули.

Для каждого идентификатора в директиве global, как и для директив public или extrn, можно также задать язык: C, FORTRAN, PASCAL, BASIC, PROLOG и NOLANGUAGE (нет языка). Это приводит к тому, что к имени идентификатора до того, как одно в объектном файле станет

22

общедоступным, автоматически применяются правила конкретного языка. Например, если вы описали

global C myprog

то идентификатор myprog в исходном файле станет общедоступным, как _myprog, поскольку по соглашениям языка Си перед именами идентификаторов следует символ подчеркивания. Использование идентификатора языка в директиве public временно отменяет текущее задание языка (используемое по умолчанию или заданное в директиве

.model).

4.5. Логические операции

Инструкции and (и), or (или), xor (исключающее или) и not (не) очень полезны при работе с отдельными битами слова или байта, а также для выполнения операций булевой алгебры.

Результаты выполнения логических операций показаны в табл. 4.1. Логическая инструкция выполняет поразрядные операции над битами исходных операндов. Например, инструкция

and ax,dx

выполняет логическую операцию and с битом 0 регистра ax и битом 0 регистра dx, затем ту же операцию с битами 1, 2 и т.д. до бита 15.

Выполнение процессором логических инструкций and, or и xor

 

 

 

 

Таблица 4.1

 

 

 

 

 

Исходный

Исходный

a and b

a or b

a xor b

бит

бит

 

 

 

a

b

 

 

 

0

0

0

0

0

0

1

0

1

1

1

0

0

1

1

1

1

1

1

0

Инструкция and устанавливает каждый бит результата (операндаприемника) в значение 1 только в том случае, если оба соответствующих бита операнда-источника равны 1. Инструкция and позволяет выделить отдельный бит или принудительно установить его в значение 0.

Инструкция or устанавливает каждый бит операнда-приемника в значение 1, если любой из соответствующих битов операнда-источника равен 1. Инструкция or позволяет принудительно установить отдельные биты (или бит) в значение 1.

Инструкция xor устанавливает каждый бит операнда-приемника в значение 1 только в том случае, если один из соответствующих битов операнда-источника равен 0, и в значение 1 в противном случае.

23

Инструкция xor позволяет "переключать" значения отдельных битов в байте. Например, инструкции:

mov al,01010101b xor al,11110000b

устанавливают регистр al в значение 10100101b или A5h. Когда для регистра al выполняется операция xor со значением 11110000b (0F0h), биты со значением 1 в 0F0h переключают значения соответствующих битов в регистре al, а биты со значением 0 оставляют соответствующие биты al неизмененными.

Инструкция xor дает удобный способ обнуления содержимого регистра. Например:

xor ax,ax

Инструкция not изменяет значение каждого бита операнда на противоположное (как если бы над исходным операндом была выполнена операция xor со значением offh). Например:

mov

bl,10110001b

not

bl

; переключить bl в 01001110b

xor

bl,0ffh

; переключить bl обратно в значение 10110001b

4.6. Циклы и переходы

Да этого момента мы рассматривали выполнение процессором 8086 инструкций в строгой последовательности. При этом каждая следующая инструкция выполнялась сразу после инструкции по предыдущему адресу. Рассматривая программу

. . .

mov ax,[BaseCount] push ax

. . .

мы могли быть уверены, что после инструкции mov будет выполнена инструкция push.

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

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

4.7. Безусловные переходы

Инструкция jmp указывает процессору 8086, что в качестве следующей за jmp инструкцией нужно выполнить инструкцию по целевой метке. Например, после завершения выполнения фрагмента программы

24

. . .

mov ax,1

jmp AddTwoToax AddOneToAx:

inc ax jmp axisSet

AddTwoToax: add ax, 2

axisSet:

. . .

регистр ax будет содержать значение 3, а инструкции add и jmp, следующие за меткой AddOneToax, никогда выполнены не будут. Здесь инструкция

jmp AddTwoToax

указывает процессору 8086, что нужно установить указатель инструкций ip в значение смещения метки AddTwoToax, поэтому следующей выполняемой инструкцией будет инструкция

add ax,2

Иногда совместно с инструкцией jmp используется операция short. Для указания на целевую метку инструкция jmp обычно использует 16-битовое смещение. Операция short указывает, что нужно 8-битовое смещение и позволяет сэкономить в инструкции jmp один байт. Например, фрагмент программы, который на два байта короче

. . .

mov ax,1

jmp short AddTwoToax AddOneToAx:

inc ax

jmp short axisSet AddTwoToax:

inc ax axisSet:

. . .

Недостаток: короткие переходы могут осуществлять передачу управления на метки, отстоящие от инструкции jmp не далее, чем на 128 байтов, поэтому в некоторых случаях ассемблер может сообщать, что метка недостижима с помощью короткого перехода. К тому же операцию short имеет смысл использовать для ссылок вперед, поскольку для переходов назад (на предшествующие метки) ассемблер автоматически

25

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

Инструкцию jmp можно использовать для перехода в другой сегмент кода, загружая в одной инструкции и регистр cs, и регистр ip. Например, в программе

. . .

CSeg1 segment

assume

cs:Cseg1

. . .

 

FarTarget

label far

. . .

 

CSeg1 ends

. . .

 

CSeg2 segment

assume

cs:cseg2

. . .

jmp FarTarget ; переход дальнего типа

. . .

CSeg2 ends

. . .

выполняется переход дальнего типа.

Если вы хотите, чтобы метка принудительно интерпретировалась, как метка дальнего типа, можно использовать операцию far ptr. Например, во фрагменте программы

. . .

jmp far ptr NearLabel nop

NearLabel:

. . .

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

4.8. Условные переходы

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

Инструкция условного перехода может осуществлять или не осуществлять переход на целевую (указанную в ней) метку, в зависимости от состояния регистра флагов. Пример:

. . .

26

mov

ah,1

; функция dos ввода с клавиатуры

int

21h

; получить следующую нажатую клавишу

cmp al,'A'

; была нажата буква "A"?

je

AWasTyped

; да, обработать ее

mov

[TampByte], al ; нет, сохранить символ

. . .

 

AWasTyped:

 

push ax

; сохранить символ в стеке

. . .

 

Для сравнения введенного символа с символом A используется инструкция cmp. Назначение данной инструкции состоит в том, чтобы можно было сравнить два операнда, установив флаги так же, как это делается в инструкции sub. Поэтому в предыдущем примере флаг нуля устанавливается в значение 1 только в том случае, если регистр al содержит символ A.

Теперь – основное. Инструкция je представляет собой инструкцию условного перехода, которая осуществляет передачу управления только в том случае, если флаг нуля равен 1. В противном случае выполняется инструкция, непосредственно следующая за инструкцией je (в данном случае – инструкция mov). Флаг нуля в данном примере будет установлен только в случае нажатия клавиши A, и только в этом случае процессор 8086 перейдет к выполнению инструкции с меткой AWasTyped, то есть

инструкции push.

 

Набор инструкций

процессора 8086 предусматривает большое

разнообразие инструкций условных переходов.

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

Перечень инструкций условных переходов приводится в табл. 4.2.

 

Инструкции условных переходов

Таблица 4.2

 

 

 

 

 

Назва-

Значение

Проверяемые

ние

 

флаги

jb/jnae

Перейти, если меньше /

cf = 1

 

перейти, если не больше или равно

 

jae/jnb

Перейти, если больше или равно / перейти, если не меньше

cf = 0

jbe/jna

Перейти, если меньше или равно / перейти, если не больше

cf = 1 или zf = 1

ja/jnbe

Перейти, если больше/

cf = 0 или zf = 0

 

перейти, если не меньше или равно

 

je/jz

Перейти, если равно

zf = 1

jne/jnz

Перейти, если не равно

zf = 0

 

27

 

Продолжение табл.4.2

Назва-

Значение

Проверяемые

ние

 

флаги

jl/jnge

Перейти, если меньше, чем / перейти, если не больше, чем

sf = of

 

или равно

 

jge/jnl

Перейти, если больше чем или равно / перейти, если не

sf = of

 

меньше чем

 

jle/jnle

Перейти, если меньше, чем или равно / перейти, если не

zf = 1 или sf =

 

больше, чем

of

jg/jnle

Перейти, если больше, чем / перейти, если не меньше, чем

zf = 0 или sf =

 

или равно

of

jp/jpe

Перейти по четности

pf = 1

jnp/jpo

Перейти по нечетности

pf = 0

js

Перейти по знаку

sf = 1

jns

Перейти, если знак не установлен

sf = 0

jc

Перейти при наличии переноса

cf = 1

jnc

Перейти при отсутствии переноса

cf = 0

jo

Перейти по переполнению

of = 1

jno

Перейти при отсутствии переполнения

of = 0

cf – флаг переноса, sf – флаг знака, of – флаг переполнения, zf – флаг нуля, pf – флаг четности

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

. . .

JumpTarget:

. . .

db

1000 dup (?)

. . .

dec

ax

jnz

JumpTarget

. . .

так как метка JumpTarget отстоит от инструкции jnz более чем на 1000 байтов. В данном случае нужно сделать следующее:

. . .

JumpTarget:

. . .

db 1000 dup (?)

. . .

dec ax

28

jnz SkipJump jmp JumpTarget

SkipJump:

. . .

Условный переход здесь применяется для того, чтобы определить, нужно ли выполнить длинный безусловные переход.

4.9. Циклы

Цикл – это блок кода, завершающийся условным переходом, благодаря чему данный блок может выполняться повторно до достижения условия завершения. Циклы служат для работы с массивами, проверки состояния портов ввода-вывода до получения определенного состояния, очистки блоков памяти, чтения строк с клавиатуры и вывода их на экран и т.д. Циклы – это основное средство, которое используется для выполнения повторяющихся действий. Поэтому используются они довольно часто в наборе инструкций процессора 8086 предусмотрено несколько инструкций циклов: loop, loopne, loope и jcxz.

Инструкция loop. Предположим, нужно вывести 17 символов текстовой строки TestString. Это можно сделать следующим образом:

. . .

 

.data

 

TestString db

'Это проверка! ...'

. . .

 

.code

 

 

. . .

 

mov

cx,17

 

mov

bx,offset TestString

PrintStringLoop:

 

mov

dl,[bx]

; получить следующий символ

inc

bx

; ссылка на следующий символ

mov

ah,2

; функция DOS вывода на экран

int

21h

; вызватьDOS для вывода символа

dec

cx

; уменьшить счетчик длины строки

jnz

PrintStringLoop ; обработать следующий символ, если он имеется

. . .

 

 

4.10. Экранные операции: основные свойства

Большинcтво программ требует ввода данных с клавиатуры, диска или модема и обеспечивает вывод данных в удобном формате на экран, принтер или диск. Данные, предназначенные для вывода на экран и ввода с клавиатуры, имеют ASCII-формат.

29

Для выполнения ввода и вывода используется команда int (прерывание). Существуют различные требования для указания системе, какое действие (ввод или вывод) и на каком устройстве необходимо выполнить. Рассмотрим основные требования для вывода информации на экран и ввода данных с клавиатуры.

Все необходимые экранные и клавиатурные операции можно выполнить, используя команду int 10h, которая передает управление непосредственно в BIOS. Для выполнения некоторых более сложных операций существует прерывание более высокого уровня int 21h, которое сначала передает управление в DOS. Например, при вводе с клавиатуры может потребоваться подсчет введенных символов, проверка на максимальное число символов и проверка на символ Enter. Преpывание DOS int 21h выполняет многие из этих дополнительных вычислений и затем автоматически передает управление в BIOS.

Материал, приведенный ниже, подходит как для монохромных (чернобелых), так и для цветных видеомониторов. В [3, 6] приведен материал для управления более совершенными экранами и для использования цвета.

4.11. Команда прерывания: int

Команда int прерывает обработку программы, передает управление в DOS или BIOS для определенного действия и затем возвращает управление в прерванную программу для продолжения обработки. Наиболее часто прерывание используется для выполнения операций ввода или вывода. Для выхода из программы на обработку прерывания и для последующего возврата команда int выполняет следующие действия:

уменьшает указатель стека на 2 и заносит в вершину стека содержимое флагового регистра;

очищает флаги tf и if;

уменьшает указатель стека на 2 и заносит содержимое регистра cs в

стек;

уменьшает указатель стека на 2 и заносит в стек значение командного указателя;

обеспечивает выполнение необходимых действий;

восстанавливает из стека значение регистра и возвращает управление

впрерванную программу на команду, следующую после int.

Этот процесс выполняется полностью автоматически. Необходимо лишь определить сегмент стека достаточно большим для записи в него значений регистров.

Рассмотрим два типа прерываний: команду BIOS int 10h, и команду DOS int 21h для вывода на экран и ввода с клавиатуры. В последующих

30

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]