первая инструкция jmp представляет собой переход дальнего типа (загружаются оба регистра cs и ip), так как это переход на метку типа far, а второй переход имеет ближний тип (загружается только регистр ip), так как это переход на метку типа near. Заметим, что обе метки FarLabel и NearLabel описывают один и тот же адрес (адрес инструкции mov), но позволяют переходить на него различными способами.
Примечание. Когда используются упрощенные директивы определения сегментов (см. уч.пос., ч.4), то директива proc – это удобный способ определить метку нужного типа (ближнюю или дальнюю) для текущей модели кода.
4.56. Перемещение данных
Данные в процессоре 8086 перемещаются с помощью инструкции mov, которая записывает копию операнда-источника в операнд-приемник. Например, инструкции:
mov |
ax,0 |
; ax := 0 |
mov |
bx,9 |
; bx := 9 |
mov |
ax,bx |
; ax := bx = 9 |
сначала записывают в регистр ax константу 0, затем в регистр bx записывается константа 9, и, наконец, содержимое bx копируется в ax.
Значение 9 не перемещается, а копируется из bx в ax.
Винструкции mov можно использовать почти любую пару операндов, что имеет смысл, за исключением того случая, когда в качестве операнда используется сегментный регистр.
Вкачестве операнда-источника (правого операнда) инструкции mov можно использовать:
–константу;
–выражение, при вычислении которого получается константа;
–общий регистр;
–ячейку памяти, доступную с помощью одного из режимов адресации.
Вкачестве операнда-приемника (левого операнда) инструкции mov может использоваться:
–общий регистр;
–ячейка памяти.
Кроме того, в качестве операнда-приемника этой инструкции можно использовать сегментный регистр, но в этом случае операндом-источником может быть только общий регистр!
4.57. Арифметические операции
Процессор 8086 не может выполнять арифметические операции с плавающей точкой (действия с такими числами, как 5.2 и 1.03Е17), не
61
говоря уже о тригонометрических функциях. Эти операции может выполнять арифметический сопроцессор 8087, но если он отсутствует, процессор 8086 выполняет программно-арифметические операции (последовательности инструкций сдвигов, сложений и проверок) и выполняет существенно медленнее.
Процессор 8086 может выполнять 8- и 16-битовое сложение, вычитание, умножение и деление чисел со знаком и без знака и имеет специальные быстрые инструкции для увеличения и уменьшения операндов. В процессоре 8086 предусмотрена также поддержка операций сложения и вычитания значений, превышающих 16 битов, хотя для операций с такими значениями требуется выполнить не одну, а несколько инструкций.
4.58.Арифметические операции над целыми двоичными числами
Карифметическим операциям над целыми двоичными числами относятся:
1) сложение двоичных чисел без знака;
2) вычитание двоичных чисел без знака;
3) умножение чисел без знака;
4) деление чисел без знака;
5) вычитание и сложение операндов большой размерности;
6) сложение двоичных чисел со знаком;
7) вычитание двоичных чисел со знаком;
8) умножение чисел со знаком;
9) деление чисел со знаком.
Здесь рассматриваются первые пять операций: четыре основных арифметических действия для двоичных чисел без знака и вычитание и сложение операндов большой размерности. Остальные операции (для двоичных чисел со знаком) будут рассматриваться в уч.пос., ч.4
4.59. Сложение целых двоичных чисел без знака
Микропроцессор выполняет сложение операндов по правилам сложения двоичных чисел. Проблем не возникает до тех пор, пока значение результата не превышает размерности поля операнда. Например, при сложении операндов размером в байт результат не должен превышать число 255. Если это происходит, то результат оказывается неверным. Рассмотрим, почему так происходит.
К примеру, выполним сложение: 254 + 5 = 259 в двоичном виде: 11111110 + 0000101 = 1 00000011. Результат вышел за пределы восьми битов и правильное его значение укладывается в 9 битов, а в 8-битовом поле операнда осталось значение 00000011, что, конечно, неверно.
62
Вмикропроцессоре этот исход сложения прогнозируется и предусмотрены специальные средства для фиксирования подобных ситуаций и их обработки. Так, для фиксирования ситуации выхода за разрядную сетку результата, как в данном случае, предназначен флаг переноса cf. Он располагается в бите 0 регистра флагов flags. Именно установкой этого флага фиксируется факт переноса единицы из старшего разряда операнда.
Естественно, что программист должен предусматривать возможность такого исхода операции сложения и средства для корректировки. Это предполагает включение участков кода после операции сложения, в которых анализируется флаг cf. Анализ этого флага можно провести различными способами. Самый простой и доступный — использовать команду условного перехода jcc. Эта команда в качестве операнда имеет имя метки в текущем сегменте кода. Переход на эту метку осуществляется
вслучае, если в результате работы предыдущей команды флаг cf установился в 1.
Всистеме команд микропроцессора имеются три команды двоичного сложения:
inc операнд ; операнд := операнд+1;
add операнд_1,операнд_2 ;операнд_1 := операнд_1 + операнд_2; adc операнд_1,операнд_2 ; сложение с учетом флага переноса cf.
;операнд_1 = операнд_1 + операнд_2 + значение_cf
Инструкция add выполняет сложение операнда-источника (правого операнда) с содержимым операнда-приемника и записывает результат в операнд-приемник.
Например, инструкции: datasg segment para ‘Data’ BaseVal dw 99
Adjustdw 10 datasg ends
codesg segment para ‘Code’
. . .
mov |
dx,BaseVal |
add |
dx,11 |
sub |
dx,Adjust |
. . .
codesg ends
сначала загружают значение, записанное в BaseVal, в регистр dx, затем прибавляют к нему константу 11 (в результате в dx получается значение 110) и, наконец, вычитают из dx значение 10, записанное в переменной Adjust. Полученное в результате значение 100 сохраняется в регистре dx.
63
Рассмотрим пример вычисления суммы чисел stacksg segment para ‘Stack’
db 256 dup (0) stacksg ends
datasg segment para ‘Data’ a db 254
datasg ends
codesg segment para ‘Code’ ;сегмент кода main:
mov ax,datasg mov ds,ax
. . .
xor ax,ax add al,17 add al,a
jnc m1 ;если нет переноса, то перейти на m1 adc ah,0 ;в ax сумма с учетом переноса
m1:
. . .
exit:
mov ax,4c00h ;стандартный выход int 21h
codesg ends
end main ;конец программы
В примере создана ситуация, когда результат сложения выходит за границы операнда. Эта возможность учитывается командой jnc, которая проверяет состояние флага cf (хотя можно было обойтись и без нее, всегда прибавляя значение флага, которое после операции add может быть равно как нулю, так и единице). Если он установлен в 1, то это признак того, что результат операции получился больше по размеру, чем размер операнда, и для его корректировки необходимо выполнить некоторые действия. В данном случае мы просто полагаем, что границы операнда расширяются до размера ax, для чего учитываем перенос в старший разряд командой adc.
Последняя команда (adc) — это команда сложения, учитывающая перенос единицы из старшего разряда. Механизм появления такой единицы рассматривался выше. Таким образом, команда adc является средством микропроцессора для сложения длинных двоичных чисел, размерность которых превосходит поддерживаемые микропроцессором длины полей.
Команды сложения чисел со знаком те же, что и для чисел без знака (подробнее см. уч.пос., ч.4).
64
4.60. Вычитание целых двоичных чисел без знака
Инструкция sub вычитает операнд-источник из операнда-приемника. Если уменьшаемое больше вычитаемого, то проблем нет, — разность положительна, результат верен. Если уменьшаемое меньше вычитаемого, возникает проблема: результат меньше 0, а это уже число со знаком. В этом случае результат необходимо завернуть. Что это означает? При обычном вычитании (в столбик) делают заем 1 из старшего разряда. Микропроцессор поступает аналогично, то есть занимает 1 из разряда, следующего за старшим, в разрядной сетке операнда. Поясним на примере.
05 = |
00000000 00000101 |
--
10 = 00000000 00001010
Для того чтобы произвести вычитание, произведем воображаемый заем из старшего разряда:
1 00000000 00000101
00000000 00001010
11111111 11111011
Тем самым по сути выполняется действие
(65 536 + 5) - 10 = 65 531,
0 здесь как бы эквивалентен числу 65 536. Результат, конечно, неверен, но микропроцессор считает, что все нормально, хотя факт заема единицы он фиксирует установкой флага переноса cf. Но посмотрите еще раз внимательно на результат операции вычитания. Это же –5 в дополнительном коде! Если представить разность в виде суммы 5 + (–10).
5 = |
00000000 00000101 |
++
(-10)= |
11111111 11110110 , |
11111111 11111011
то получим тот же результат, что и в предыдущем примере.
Таким образом, после команды вычитания чисел без знака нужно анализировать состояние флага cf. Если он установлен в 1, то это говорит о том, что произошел заем из старшего разряда и результат получился в дополнительном коде.
Аналогично командам сложения группа команд вычитания состоит из минимально возможного набора. Эти команды выполняют вычитание по алгоритмам, которые мы сейчас рассматриваем, а учет особых ситуаций должен производиться самим программистом. К командам вычитания относятся следующие:
dec операнд ; операнд := операнд - 1;
sub операнд_1,операнд_2 ; операнд_1 := операнд_1 – операнд_2;
65