- FasmWorld - https://fasmworld.ru -

Учебный курс. Часть 10. Сложение и вычитание с переносом

В системе команд процессоров x86 имеются специальные команды сложения и вычитания с учётом флага переноса (CF). Для сложения с учётом переноса предназначена команда ADC [1], а для вычитания — SBB [2]. В общем, эти команды работают почти также, как ADD [3] и SUB [4], единственное отличие в том, что к младшему разряду первого операнда прибавляется или вычитается дополнительно значение флага CF.

Зачем нужны такие команды? Они позволяют выполнять сложение и вычитание многобайтных целых чисел, длина которых больше, чем разрядность регистров процессора (в нашем случае 16 бит). Принцип программирования таких операций очень прост — длинные числа складываются (вычитаются) по частям. Младшие разряды складываются(вычитаются) с помощью обычных команд ADD [3] и SUB [4], а затем последовательно складываются(вычитаются) более старшие части с помощью команд ADC [1] и SBB [2]. Так как эти команды учитывают перенос из старшего разряда, то мы можем быть уверены, что ни один бит не потеряется 🙂 Этот способ похож на сложение(вычитание) десятичных чисел в столбик.

На следующем рисунке показано сложение двух двоичных чисел командой ADD [3]:

При сложении происходит перенос из 7-го разряда в 8-й, как раз на границе между байтами. Если мы будем складывать эти числа по частям командой ADD [3], то перенесённый бит потеряется и в результате мы получим ошибку. К счастью, перенос из старшего разряда всегда сохраняется в флаге CF. Чтобы прибавить этот перенесённый бит, достаточно применить команду ADC [1]:

Аналогичная ситуация возникает с вычитанием чисел по частям. Чтобы было совсем понятно, приведу пример программы. Допустим, требуется вычислить значение формулы k=i+j-n+1, где переменные k, i, j и n являются 32-битными целыми числами без знака. Складывать и вычитать такие числа придётся в два этапа: сначала вычисления будут производиться с младшими словами операндов, а затем со старшими с учётом переноса.

Для прибавления единицы в данном примере нельзя использовать команду INC [5], так как она не влияет на флаг CF и мы можем получить ошибку в результате!

use16                 ;Генерировать 16-битный код
org 100h              ;Программа начинается с адреса 100h

    mov ax,word[i]    ;Загружаем младшую часть i в AX
    mov bx,word[i+2]  ;Загружаем старшую часть i в BX

    add ax,word[j]    ;Складываем младшие части i и j
    adc bx,word[j+2]  ;Складываем старшие части i и j

    sub ax,word[n]
    sbb bx,word[n+2]  ;BX:AX = i+j-n

    add ax,1          ;Команда INC здесь не подходит!
    adc bx,0          ;BX:AX = i+j-n+1

    mov word[k],ax    ;\
    mov word[k+2],bx  ;/ Сохраняем результат в k

    mov ax,4C00h      ;\
    int 21h           ;/ Завершение программы
;-------------------------------------------------------
i dd 120000
j dd  80500
n dd   2300
k dd      ?

Запись word[i] означает, что мы переопределяем размер переменной (она объявлена как DWORD) и обращаемся к младшему слову. Старшее слово расположено в памяти после младшего, поэтому к адресу переменной надо прибавить 2, и соответствующая запись будет иметь вид word[i+2].

Посмотреть работу программы можно в отладчике:

Обратите внимание, как хранятся переменные в памяти. В процессорах Intel младший байт всегда хранится по младшему адресу, поэтому получается, что в окне дампа значения надо читать справа налево. В регистрах же числа записываются в нормальном виде. Сравните, как выглядит одно и то же значение k в памяти и в регистрах (старшая часть находится в BX, а младшая — в AX).

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

use16                 ;Генерировать 16-битный код
org 100h              ;Программа начинается с адреса 100h

    mov ax,word[x]
    add ax,word[y]
    mov word[z],ax

    mov ax,word[x+2]
    adc ax,word[y+2]
    mov word[z+2],ax

    mov ax,word[x+4]
    adc ax,word[y+4]
    mov word[z+4],ax

    mov al,byte[x+6]
    adc al,byte[y+6]
    mov byte[z+6],al

    mov ax,4C00h      ;\
    int 21h           ;/ Завершение программы
;-------------------------------------------------------
x dd 0xF1111111
  dw 0xF111
  db 0x11
y dd 0x22222222
  dw 0x2222
  db 0x22
z rb 7

Обращение к старшему байту записывается как byte[x+6]. Наверно, вы уже догадались, почему 🙂 Команда MOV [6] не меняет состояние флагов, поэтому её можно ставить между командами сложения.

Упражнение

Напишите программу для вычисления формулы d=b-1+a-c. Все числа — 3-х байтные целые без знака. Проверьте работу программы в отладчике. Результаты можете выкладывать в комментариях.

Следующая часть » [7]