Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
flor_apparato-orientirovnnoe_prog.doc
Скачиваний:
89
Добавлен:
15.06.2014
Размер:
926.72 Кб
Скачать

3.3. Особенности команд умножения и деления

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

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

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

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

Было принято следующее правило: результат умножения 8-битных чисел размешается в регистре AX, результат умножения 16-битных чисел размещается в паре регистров AX и DX, причем в DX размещаются старшие разряды произведения, а в регистре AX - его младшие разряды. При умножении 32-битных чисел старшие 32 бита произведения помещается в регистр EDX, а младшие 32 бита этого произведения помещаются в регистре EAX.

Такая специализация регистров для результата повлияла и на дополнительное решение данной архитектуры (связанное опять с микропроцессорными проблемами упрощения аппаратуры и машинных кодов). Было принято (хотя это совершенно необязательно) требовать предварительного размещения множимого в одном из регистров, согласно установленным правилам. Именно, при умножении 8-битных чисел первый сомножитель должен помещаться в регистр AL, при умножении 16-битных чисел - в регистр AX, а при умножении 32-битных в регистр EAX.

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

Следствием перечисленных соглашений стала, в частности, неявная интерпретация следующих вариантов записи команд умножения, которое для беззнаковых операндов задается мнемоникой MUL. Команда, записанная как MUL DH, выполняет умножение содержимого регистра AL на содержимое регистра DH, размещая результат в регистре AX. Команда MUL DI выполняет умножение содержимого регистра AX на содержимое регистра DI размещая результат в паре регистров DX, AX. Команда MUL DWORD [fooo] выполняет умножение содержимого регистра EAX на значение числа, записанного в области из четырех байтах, начало которой обозначено именем fooo.

Для умножения двоичных чисел с учетом знака предназначена команда с мнемоником IMUL, которая также в простейшем варианте имеет единственный операнд - множитель (но может и использоваться с двумя или тремя операндами в форме IMUL результат, множитель или IMUL результат, множимое, множитель; эту форму мы использовать и подробно рассматривать не будем).

Принятые соглашения по командам умножения повлекли закономерные последствия для команд деления. Эти последствия связаны с тем общеизвестным фактом, что деление есть операция обратная к умножению, поэтому даже в машинной архитектуре желательно, чтобы после умножения можно было просто и естественно выполнить обратное действие без вспомогательных команд. Поэтому было принято, что исходное значение делимого для деления на 8-битное число должно размещаться в регистре AX, исходное значение делимого для 16-битного деления - в паре регистров DX, AX, а исходное значение делимого для 32-битного деления - в паре регистров EDX, EAX (именно там, где они оказались при соответствующем умножении). Сама команда деления задается при этом мнемокодом DIV, имеет один операнд, который задает делимое, а результат деления опять же размещается согласно принятым соглашениям. Эти дополнительные соглашения, основанные на исходных соглашениях для деления, следующие. Частное при 8-битном делении помещается аппаратурой в регистр AL, а остаток при этом делении - в регистр AH. При 16-битном делении частное помещается в регистр AX, а остаток от деления - в регистр DX. При 32-битном делении для размещения результата используются, соответственно, регистры EAX и EDX. Для деления чисел с учетом знаков предназначена команда с мнемокодом IDIV, которая по использованию операндов полностью совпадает с рассмотренной командой DIV.

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

Применим рассмотренную информацию к решению следующей практической задачи. Операционные системы в составе своего программного интерфейса не содержат средств ввода и вывода данных числовых форматов. Их программный интерфейс позволяет вводить и выводить только отдельные символы и тексты из таких символов, а результаты расчетов образуются программами в двоичных кодах, которые совсем не похожи на внешние коды отдельных цифр. Ввод трех цифр числа 375 должен приводить к построению двоичного кода 0000000101110111 для представления этого числа в двоичной системе счисления, а сами вводимые цифры имеют двоичные коды 00110011, 00110111, 00110101. Из этого примера видно, что преобразование между внешним представлением числа в виде последовательности кодов цифр и представлением в двоичной системе счисления оказывается далеко не тривиальным. С другой стороны, такие преобразования оказываются необходимыми из-за отсутствия для них средств в операционной системе.(Отсутствие же последних в ОС обусловлено многообразием возможных форматов для множества типов данных, которые используются в различных языках программирования.) Такие средства входят в стандартные библиотеки языков программирования высокого уровня, в частности, в стандартную библиотеку языка Си. При разработке программ на ассемблере разработчик оказывается перед проблемой построения подпрограмм ввода и вывода чисел. Этой проблемой в частном варианте мы и займемся.

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

Напомним, что получение значений цифр для другой системы счисления достигается последовательно путем деления исходного числа и промежуточных частных на основание целевой системы счисления. На каждом шаге такого процесса вычисления получаемый остаток и дает очередную цифру результата. Существенной особенностью этого процесса является то, что вначале получаются младшие цифры результата, а только затем старшие. Иначе говоря, цифры результата получаются в очередности, обратной их использованию в записи числа цифрами слева направо. Например, при последовательном делении на основание 10 числа 375 и промежуточных частных вначале образуется значение цифры 5, затем 7 и только потом старшей цифры 3. Такая особенность заставляет в приводимой далее программе запоминать промежуточные цифры результата, чтобы на завершающем этапе извлекать их из места хранения в обратной последовательности. Для такого запоминая с обратным порядком извлечения удобно использовать стек. Предлагаемая программа приведена в листинге 3.3.1.

GLOBAL _start

SEGMENT .text

_start: mov eax, 0ffffffffh

mov esi,10 ; base of position digit system

mov ecx, 0 ; reset digit counter

pov: mov edx, 0 ; null into left part of devident

div esi ; divide for next digit = rest

add dl, '0'

push edx

inc ecx ; step into counter

cmp eax, 0

jne pov

mov [cnt], ecx

izv: pop edx

mov [digit],dl ; digit into digit for write

mov eax,4 ; N function=write

mov ebx,1 ; N handle=1 (stdout)

mov ecx, digit; address of digit

mov edx, 1 ; number of byte

int 80h

dec dword [cnt]

cmp dword [cnt],0

jne izv

mov eax, 1 ; N function = exit

int 80h ;

SEGMENT .data

digit db 0

cnt dd 0

Листинг 3.3.1. Вывод десятичного значения содержимого EAX в Linux

В связи с изученными выше особенностями команд умножения и деления, в данной программе основание результирующей системы счисления перед использованием заносится в регистр ESI. (Делитель для команды деления не может в данной архитектуре быть задан константой в этой команде.) В качестве подготовительного действия обнуляется регистр ECX, который далее используется как счетчик выделенных десятичных цифр. На начальном шаге тела цикла обнуляется значение регистра EDX. Напомним, что при 32-битном делении в этом регистре должны быть старшие биты делимого удвоенной разрядности по сравнению с используемой (старшие 32 бита из 64 битного делителя). В нашем частном случае производится деление исходного 32-битного значения и 32-битных промежуточных результатов. Если мы не позаботимся об систематическом обнулении старших битов, которые аппаратура все равно для данной разрядности будет использовать (хотим мы этого или нет), то ненулевые биты в этом регистре, полученные в ходе деления, будут искажать желаемый результат.

Собственно получение значения очередной десятичной цифры осуществляется командой DIV ESI. В результате ее выполнения остаток получается в регистре EDX, но нам достаточно учитывать, что в действительности его ненулевые биты занимают только регистр DL (значение этого остатка находится в пределах от 0 до 9). В дальнейшем для вывода нам потребуется не собственно значение остатка, а значение цифры, отвечающее этому остатку. Для преобразования достаточно прибавить к содержимому регистра DL значение кода цифры 0. Если остаток был нуль, то и получится код цифры 0. В таблице кодировки символов все цифры идут подряд, поэтому в общим случае к коду цифры 0 прибавится значение (от 0 до 9), что дает код соответствующей цифры. В данной команде можно было прибавлять код цифры 0 не обязательно к содержимому регистра DL, а можно было прибавлять в содержимому регистра EDX или DX - результат был бы тем же.

Полученное значение кода цифры временно запоминается в стеке. Для этого выполняется команда PUSH EDX (команды PUSH DL в данной архитектуре нет, а код команды PUSH DX, если вникать в особенности реализации, занимает даже больше места, чем PUSH EDX для 32-битного исполняемого кода). Тут же увеличивается значение регистра ECX для подсчета только что вычисленной и запомненной цифры результата.

Далее определяется, следует ли вычислять более старшие цифры результата. Если полученное промежуточное частное не равно нулю, из него нужно вычислить эти цифры, иначе все значащие цифры уже найдены. Если требуется продолжать вычисления следующих цифр, то, как легко видеть, нет необходимости в какой-то подготовке следующего шага вычислений, отличной от уже имеющихся в начале программы. Частное от промежуточного деления уже находится в регистре EAX, где его и следует иметь для очередного деления командой DIV ESI.

По вычислению всех цифр результата значение регистра ECX, в котором получено число таких цифр, запоминается в именованной области cnt с помощью команды MOV [cnt], ecx. Затем из стека извлекается очередная, запомненная ранее цифра (командой POP EDX). Ее значение из младших битов регистра EDX перемещается во вспомогательное однобайтовое поле digit, и выполняется системная функция write для вывода этой одной цифры из служебного поля (системная функция write не позволяет задать вывод содержимого непосредственно из регистра).

После этого значение переменной cnt счетчика для числа цифр, оставшихся не выведенными, уменьшается на единицу и проверяется, не достигла ли она значения 0. Если это значение еще не нулевое, выполняется повторение тела цикла, начинающееся с извлечения из стека очередной цифры результата.

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

У начинающего программиста может возникнут вопрос, нельзя ли было вместо деления 64-битного кода из пары регистров EDX, EAX использовать деление исходного 32-битного числа путем деления на 16-битный делитель, помещенного, например, в регистре DX ? Конечно, при этом пришлось бы переместить исходное число из регистра EAX в пару регистров DX, AX. Казалось бы, так можно было несколько упростить программу. Тем не менее, последнее решение привело бы к неправильным результатам. Дело в том, что после первого 16-битного деления промежуточное частное (значение, полученное из исходного числа отбрасыванием младшей десятичной цифры) может не поместиться в регистре AX, и аппаратурой будет обнаруживаться ошибка деления.

Соседние файлы в предмете Системное программное обеспечение