Деление и умножение в Assembler
Здравствуйте, уважаемые друзья! Продолжаем изучать нашу рубрику, на очереди тема умножения и деления в Assembler. Разберемся со всеми тонкостями этих операций, конечно же, на практическом примере.
Основные команды
- Для умножения в Assembler используют команду
mul
- Для деления в Assembler используют команду
div
Правила умножения в Assembler
Итак, как мы уже сказали, при умножении и делении в Assembler есть некоторые тонкости, о которых дальше и пойдет речь. Тонкости эти состоят в том, что от того, какой размерности регистр мы делим или умножаем многое зависит. Вот примеры:
- Если аргументом команды mul является 1-байтовый регистр (например
mul bl
), то значение этого регистра bl умножится на значение регистра al, а результат запишется в регистр ax, и так будет всегда, независимо от того, какой 1-байтовый регистр мы возьмем.bl*al = ax
- Если аргументом является регистр из 2 байт(например
mul bx
), то значение в регистре bx умножится на значение, хранящееся в регистре ax, а результат умножения запишется в регистр eax.bx*ax = eax
- Если аргументом является регистр из 4 байт(например
mul ebx
), то значение в регистре ebx умножится на значение, хранящееся в регистре eax, а результат умножения запишется в 2 регистра: edx и eax.ebx*eax = edx:eax
Правила деления в Assembler
Почти аналогично реализуется и деление, вот примеры:
- Если аргументом команды div является 1-байтовый регистр (например
div bl
), то значение регистра ax поделится на значение регистра bl, результат от деления запишется в регистр al, а остаток запишется в регистр ah.ax/bl = al, ah
- Если аргументом является регистр из 2 байт(например
div bx
), то процессор поделит число, старшие биты которого хранит регистр dx, а младшие ax на значение, хранящееся в регистре bx. Результат от деления запишется в регистр ax, а остаток запишется в регистр dx.(dx,ax)/bx = ax, dx
- Если же аргументом является регистр из 4 байт(например
div ebx
), то процессор аналогично предыдущему варианту поделит число, старшие биты которого хранит регистр edx, а младшие eax на значение, хранящееся в регистре ebx. Результат от деления запишется в регистр eax, а остаток запишется в регистр edx.(edx,eax)/ebx = eax, edx
Программа
Далее перейдем к примеру: он не должен вызвать у вас каких либо затруднений, если вы читали наши предыдущие статьи, особенно важна статья про вывод на экран, советую вам с ней ознакомиться. Ну а мы начнем:
.386 .model flat,stdcall option casemap:none include ..\INCLUDE\kernel32.inc include ..\INCLUDE\user32.inc includelib ..\LIB\kernel32.lib includelib ..\LIB\user32.lib BSIZE equ 15 .data ifmt db "%d", 0 ;строка формата stdout dd ? cWritten dd ? CRLF WORD ? .data? buf db BSIZE dup(?) ;буфер
Стандартное начало, в котором мы подключаем нужные нам библиотеки и объявляем переменные для вывода чисел на экран. Единственное о чем нужно сказать: новый для нас раздел .data? Знак вопроса говорит о том, что память будет выделяться на этапе компилирования и не будет выделяться в самом исполняемом файле с расширением .exe (представьте если бы буфер был большего размера) . Такое объявление — грамотное с точки зрения программирования.
.code start: invoke GetStdHandle, -11 mov stdout,eax mov CRLF, 0d0ah ;-------------------------деление mov eax, 99 mov edx, 0 mov ebx, 3 div ebx invoke wsprintf, ADDR buf, ADDR ifmt, eax invoke WriteConsoleA, stdout, ADDR buf, BSIZE, ADDR cWritten, 0 invoke WriteConsoleA, stdout, ADDR CRLF, 2, ADDR cWritten,0
В разделе кода, уже по традиции, считываем дескриптор экрана для вывода и задаем значения для перевода каретки. Затем помещаем в регистры соответствующие значения и выполняем деление регистра ebx, как оно реализуется описано чуть выше. Думаю, тут понятно, что мы просто делим число 99 на 3, что получилось в итоге выводим на экран консоли.
;-------------------------умножение mov bx, 4 mov ax, 3 mul bx invoke wsprintf, ADDR buf, ADDR ifmt, eax invoke WriteConsoleA, stdout, ADDR buf, BSIZE, ADDR cWritten, 0 invoke ExitProcess,0 end start
Думаю, что здесь тоже все понятно и без комментариев. Как производиться умножение в Assembler вы тоже можете прочитать чуть выше, ну и результат выводим на экран.
Просмотр консоли
Этот код я поместил в файл seventh.asm, сам файл поместил в папку BIN (она появляется при установке MASM32). Далее открыл консоль, как и всегда, с помощью команды cd
перешел в эту папку и прописал amake.bat seventh
. Скомпилировалось, затем запускаю исполняемый файл и в консоли получаются такие числа:
Как видите, мы правильно посчитали эти операции.
На этом сегодня все! Надеюсь вы научились выполнять деление и умножение на Assembler.
Скачать исходники
Команда DIV
Лучшие книги по Ассемблеру
Сделал подборку не новых, но проверенных книг по программированию на языке ассемблера. Если вы также как и я любите погружаться на низкий уровень, в те закоулки мира программирования, куда не всем путь открыт, то посмотрите. Возможно, что-то вам понравится. Подробнее… |
Инструкция DIV в Ассемблере выполняет деление без знака. Использование этой инструкции похоже на работу команды MUL, хотя, конечно, имеет некоторые особенности, потому что деление — это не умножение )))
Итак, синтаксис команды DIV такой:
DIV ЧИСЛО
ЧИСЛОМ может быть один из следующих:
- Область памяти (MEM)
- Регистр общего назначения (REG)
Эта команда не работает с сегментными регистрами, а также не работает непосредственно с числами. То есть вот так
DIV 200 ; неправильно
делать нельзя.
А теперь алгоритм работы команды DIV:
- Если ЧИСЛО — это БАЙТ, то AL = AX / ЧИСЛО
- Если ЧИСЛО — это СЛОВО, то AX = (DX AX) / ЧИСЛО
Если вы уже изучили инструкцию MUL, то ничего особо нового для вас здесь нет. Ну а если не изучали, то немного напомню.
Обратите внимание, что инструкция DIV работает либо с регистром АХ, либо с парой регистров DX AX. То есть перед выполнением этой команды нам надо записать в регистр АХ или пару регистров DX AX значение, которое требуется разделить. Сделать это можно, например, с помощью уже известной нам команды MOV.
Затем надо в область памяти или в регистр общего назначения записать делитель — то есть число, на которое будем делить.
Далее мы выполняем деление, и получаем результат либо в регистр АL (если ЧИСЛО — это байт), либо в регистр AX (если ЧИСЛО — это слово).
Остаток от деления
Как вы понимаете, инструкция DIV выполняет целочисленное деление. При этом остаток от деления, если таковой имеется, будет записан:
- В регистр АН, если ЧИСЛО — это байт
- В регистр DX, если ЧИСЛО — это слово
Никакие флаги при этом не изменяются. А если и меняются, то об этом ничего не сказано в документации, следовательно, проверять флаги нет необходимости.
Просто если есть сомнения, что деление выполнено без остатка, надо проверить содержимое регистров AL или DX в зависимости от того, какой размер имеет ЧИСЛО.
Пример деления в Ассемблере
Итак, например, нам надо 250 разделить на 150. Тогда мы делаем так:
MOV AX, 250 ; Делимое в регистр AX MOV BL, 150 ; Делитель в регистр BL DIV BL ; Теперь АL = 250 / 150 = 1, AH = 100
Обратите внимание, что нам приходится два раза использовать команду MOV, так как команда DIV не работает непосредственно с числами, а только с регистрами общего назначения или с памятью.
После выполнения этого кода в регистре АL будет результат целочисленного деления числа 250 на число 150, то есть число 1, а в регистре АН будет остаток от деления — число 100 (64 в шестнадцатеричной системе).
Теперь попробуем число 50000000 разделить на 60000.
MOV DX, 762 ; Делимое - в пару регистров DX AX MOV AX, 61568 ; (DX AX) = 50000000 MOV BX, 60000 ; Делитель в регистр BX DIV BX ; Теперь АХ = 50000000 / 60000 = 833 (341h) ; DX = 20000 (4E20h)
Для записи делителя в пару регистров DX и AX используются две команды MOV. В нашем примере в регистр DX будет записано число 762 (02FA — в шестнадцатеричной системе), а в регистр АХ — число 61568 (F080 — в шестнадцатеричной системе). А если рассматривать их как одно число (двойное слово), где в старшем слове 762, а в младшем — 61568, то это и будет 50000000 (2FAF080 — в шестнадцатеричной системе).
Затем в регистр BX мы записываем число 60000 и выполняем команду деления. В результате в регистре АХ будет число 833 (или 341 в шестнадцатеричной системе), в регистре DX — остаток от деления, который в нашем случае будет равен 20000 (или 4E20 в шестнадцатеричной системе).
В конце как обычно расскажу, почему эта команда ассемблера называется DIV. Это сокращение от английского слова DIVIDE, которое можно перевести как “разделить”.
Подписаться на Дзен-канал
Вступить в группу «Основы программирования» Подписаться на рассылки по программированию |
Первые шаги в программирование
Главный вопрос начинающего программиста – с чего начать? Вроде бы есть желание, но иногда «не знаешь, как начать думать, чтобы до такого додуматься». У человека, который никогда не имел дело с информационными технологиями, даже простые вопросы могут вызвать большие трудности и отнять много времени на решение. Подробнее… |
сборка — Разделение на ассемблере x86
спросил
Изменено 4 года, 4 месяца назад
Просмотрено 26 тысяч раз
Мой колледж дал мне упражнение:
1. Создайте новый документ в Jasmin
2. Используйте AL-Register, чтобы добавить 9до 8.
3. Вычесть 2.
4. Разделить на 7.
Мое решение:
mov al,9 добавить аль,8 саб аль, 2
Но как мне разделить на 7? Я пробовал что-то вроде div al,7
, но это не работает.
- сборка
- x86
div
операция делит (без знака) значение в регистрах AX, DX:AX или EDX:EAX (делимое) на исходный операнд (делитель) и сохраняет результат в AX (AH:AL), DX:AX или EDX:EAX.
source
итак, чтобы разделить значение на al, нужно сделать:
mov ah, 0 # очистить ah, также можно сделать это раньше, как move ax, 9 mov bl, 7 # подготовить делитель div bl # al = ax / bl, ah = ax % bl
после этого al будет содержать частное, а ah будет содержать остаток
6
Существует инструкция DIV
, которая выполняет деление, но вам нужно сначала поместить делимое в AX
(или один из его братьев и сестер).
Инструкции div
и idiv
не имеют форм, которые принимают немедленную форму. Они принимают только один явный операнд (регистр или память), а делимое неявно в AX, или DX:AX, EDX:EAX или RDX:RAX. См. этот ответ, чтобы узнать, как их использовать.
Но x86 в 16- и 32-битном режиме имеет инструкцию прямого деления, и на самом деле она немного быстрее, чем div r/m8
на процессорах Intel до Skylake:
разделит AL на непосредственную 7, помещая частное в AH, остаток в AL. (https://agner.org/optimize/ говорит, что на Haswell задержка на 1 такт ниже, а пропускная способность на 1 такт выше, чем у div r8
. И это 8 мопов вместо 9.)
Обратите внимание, что это отличается от mov cl, 7
/ div cl
, что берет все AX в качестве делимого и помещает частное в AL, остаток в AH .
AAM недоступен в 64-битном длинном режиме , удален вместе с другими менее полезными устаревшими инструкциями BCD. Но если это сэкономит количество мопов в целом (включая mov
непосредственно к реестру), это может быть полезно. На Skylake он стоит 11 мкп против 10 у div r8
, а пропускная способность на 1с хуже , и такая же латентность.
Этот код работает только для разделения однозначных чисел.
.модель маленькая .данные msg1 db 'введите дивиденд:$' msg2 db 10,13,'введите делитель:$' результат БД 10,13,'результат:$' дивиденды дб ? делитель дб? .код .запускать мов топор,@данные мов дс, топор мов ах, 9 lea dx, msg1 через 21 час мов ах, 1 через 21 час саб аль, 30ч mov дивиденды, al мов ах, 9lea dx, msg2 через 21 час мов ах, 1 через 21 час саб аль, 30ч mov divisor , al движение, дивиденды мов бл, делитель мов ах,0 раздел бл мов ах, 9 lea dx, результат через 21 час мов ах, 2 добавить al,30h мов дл, аль через 21 час мов ах, 4ch через 21 час конец
Зарегистрируйтесь или войдите в систему
Зарегистрируйтесь с помощью Google
Зарегистрироваться через Facebook
Зарегистрируйтесь, используя адрес электронной почты и пароль
Опубликовать как гость
Электронная почта
Обязательно, но не отображается
Опубликовать как гость
Электронная почта
Требуется, но не отображается
Деление в х86 сборке ГАЗ
Задавать вопрос
спросил
Изменено 6 лет, 7 месяцев назад
Просмотрено 18 тысяч раз
Я еще не совсем уверен, как деление работает в ассемблере x86 (синтаксис GAS AT&T). Что я хочу сделать, так это разделить два длинных числа, а затем умножить частное на делитель, чтобы увидеть, равно ли новое число исходному числу (n/m * m = n).
мовл %ebx, %eax мовл %ecx, %edx идивл %edx %ebx, %edx cmp %edx, %ebx je .равный
Приведенный выше код представляет собой фрагмент кода, в котором я выполняю деление. ebx и ecx — это два счетчика, которые я хочу разделить, правильно ли, что регистр eax используется в качестве делителя? поэтому, когда я пишу idivl %edx, я делю edx на eax и получаю целое число, ближайшее к 0? Типа 7/2 = 3? Я читал в одном месте, что частное хранится в регистре edx, а остаток в регистре ah, но мне также сказали, что частное хранится в регистре eax, а остаток в регистре edx, что сбило меня с толку.
Хотя главный вопрос здесь таков: я хочу разделить значение регистра ebx на значение регистра ecx, как мне поступить?
Спасибо!
РЕДАКТИРОВАТЬ: приведенный выше код дает исключение с плавающей запятой
- сборки
- x86
- att
7
Инструкция idiv
принимает 2 аргумента в 3 регистрах.
Первый неявный аргумент — это делимое, 64-битный аргумент в edx:eax
Младшие 32 бита в eax
, старшие 32 бита в edx
.
Второй явный аргумент — это делитель, 32-битный аргумент в одном регистре.
По понятным причинам делитель , а не должен быть edx
или eax
.
Результат возвращается в формате eax = частное, edx = остаток.
Зная это, правильная настройка должна быть следующей:
Синтаксис Intel Синтаксис pdp-11 -------------------------------------------------- ------ .intel_syntax без префикса mov ebx, дивиденды mov ecx, делитель mov eax,ebx movl %ebx, %eax cdq cdq //edx:eax = 32-битное делимое idiv ecx idivl %ecx //делим edx:eax на ecx imul eax, ecx imull %ecx, %eax //умножить результат на делимое cmp ebx, eax cmpl %eax, %ebx je .равно je .равно add eax,edx addl %edx, %eax //добавить остаток cmp ebx,eax cmpl %eax,%ebx //теперь должно быть равно je . equal2 je .equal2
Следует помнить, что div/idiv
выполняет целочисленное деление!
Результатом всегда является целое число с остатком (который может быть равен нулю).
Не работает с плавающей запятой. Исключение генерируется только в том случае, если результат слишком велик, чтобы уместиться в 32 бита, или если вы делите на ноль, и в этом случае вы получаете ошибку #DE Division.
Причина, по которой вы получаете ошибку целочисленного деления , заключается в том, что вы ошибочно используете edx
в качестве делителя, и потому что ваше делимое 32-битное, старшие 32 бита (хранящиеся в edx
) всегда равны нулю, и поэтому у вас есть деление на ноль.
Никогда не используйте одни и те же регистры для делимого и делителя!
Вы получите ту же ошибку, если edx:eax idiv ecx
не умещается в 32 бита (т. е. если edx:eax
слишком велик по сравнению с ecx
).
См.