The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Six (Part 1)

Table of Content

Chapter Six (Part 3)

CHAPTER SIX:
THE 80x86 INSTRUCTION SET (Part 2)
6.5 - Arithmetic Instructions
6.5.1 - The Addition Instructions: ADD, ADC, INC, XADD, AAA, and DAA
6.5.1.1 - The ADD and ADC Instructions
6.5.1.2 - The INC Instruction
6.5.1.3 - The XADD Instruction
6.5.1.4 - The AAA and DAA Instructions
6.5.2 - The Subtraction Instructions: SUB, SBB, DEC, AAS, and DAS
6.5.3 - The CMP Instruction
6.5.4 - The CMPXCHG, and CMPXCHG8B Instructions
6.5.5 - The NEG Instruction
6.5.6 - The Multiplication Instructions: MUL, IMUL, and AAM
6.5.7 - The Division Instructions: DIV, IDIV, and AAD
6.5 Instruções Aritméticas

O 80x86 provê muitas operações aritméticas: adição, subtração, negação, multiplicação, divisão/módulo (resto), e comparaçaõ de dois valores. As instruções que manuseiam essas operações são add, adc, sub, sbb, mul, imul, div, idiv, cmp, neg, inc, dec, xadd, cmpxchg e algumas instruções de conversões mistas: aaa, aad, aam, aas, daa e das. As seções seguintes descrevem essas instruções em detalhes.

As formas genéricas dessas instruções são

add     dest, src               dest := dest + src
adc     dest, src               dest := dest + src + C
SUB     dest, src               dest := dest - src
sbb     dest, src               dest := dest - src - C
mul     src                     acc := acc * src
imul    src                     acc := acc * src
imul    dest, src1, imm_src     dest := src1 * imm_src
imul    dest, imm_src           dest := dest * imm_src
imul    dest, src               dest := dest * src
div     src                     acc := xacc /-mod src
idiv    src                     acc := xacc /-mod src
cmp     dest, src               dest - src (e seta os flags)
neg     dest                    dest := - dest
inc     dest                    dest := dest + 1
dec     dest                    dest := dest - 1
xadd    dest, src               (veja texto)
cmpxchg  operando1, operando2     (veja texto)
cmpxchg8 ax, operando           (veja texto)
aaa                             (veja texto)
aad                             (veja texto)
aam                             (veja texto)
aas                             (veja texto)
daa                             (veja texto)
das                             (veja texto)
6.5.1 As Instruções de Adição: ADD, ADC, INC, XADD, AAA, e DAA

Essas instruções tomam as formas:

                add     reg, reg
                add     reg, mem
                add     mem, reg
                add     reg, dado imediato
                add     mem, dado imediato
                add     eax/ax/al, dado imediato

                as formas de adc são idênticas às de ADD.

                inc     reg
                inc     mem
                inc     reg16
                xadd    mem, reg
                xadd    reg, reg
                aaa
                daa

Note que aaa e daa usam o modo de endereçamento implícito e não permitem operandos.

6.5.1.1 As instruções ADD e ADC

A sintaxe de add e adc é similar à deo mov. Como mov, há formas especiais para o registrador ax/eax que são mais eficientes. Diferentemente do mov, você não pode adicionar um valor a um registrador de segmentos com essas instruções.

A instrução add soma o conteúdo do operando fonte ao operando destino. Por exemplo, "add ax, bx" adiciona bx a ax deixando a soma no registrador ax. ADD computa destino:=destino+fonte enqanto ADC computa destino:=destino+fonte+C, onde C representa o valor no flag de vai-um (carry). Portanto, se o flag de carry estiver zerado antes da execução, ADC se comporta exatamente como o ADD.

Ambas as instruções afetam os flags idênticamente. Elas setam os flags como se segue:

As instruções ADD e ADC não afetam outros flags.

As instruções ADD e ADC permitem operandos de 8, 16 e (no 80386 em diante) 32 bits. Tanto o operando fonte quanto o destino devem ser do mesmo tamanho. Veja o Capítulo Nove se você quiser adicionar operandos cujo tamanho seja diferente.

Já que não existe adição de memória para memória, você tem que caregar operandos de memória para registradores se quiser adicionar duas variáveis. O seguinte código exemplifica as possíveis formar da instrução add:

; J:= K + M

                mov     ax, K
                add     ax, M
                mov     J, ax

Se você quiser adicionar váris valores, você pode facilmente calcular a soma em único registrador:

; J := K + M + N + P

                mov     ax, K
                add     ax, M
                add     ax, N
                add     ax, P
                mov     J, ax

Se você quiser reduzir o número de perigos num processador 486 ou Pentium, você pode usar código como o seguinte:

                mov     bx, K
                mov     ax, M
                add     bx, N
                add     ax, P
                add     ax, bx
                mov     J, ax

Uma coisa que programadores principiantes em assembly freqüentemente se esquecem é que você pode somar um registrador a uma posição na memória. Às vezes progamadores principiantes até acreditam que ambos os operadores precisam ser em registradores, esquecendo completamente as lições do Capítulo Quatro. O processador 80x86 é um processador CISC que lhe permite usar os modos de endereçamento de memória com várias instruções, como o ADD. É geralmente mais eficiente usar as vantagens das capacidades de endereçamento de memória do 80x86.

; J := K + J

                mov     ax, K           ; Isso funciona porque a adição é
                add     J, ax           ; comutativa!

; Freqüentemente, os principiantes vão codificar isso aí acima como uma das seguintes seqüências.
; Isso é desnecesário!

                mov     ax, J           ; Modo realmente RUIM de calcular
                mov     bx, K           ; J := J + K.
                add     ax, bx
                mov     J, ax

                mov     ax, J           ; Melhor, mas ainda nã é um bom modo de 
                add     ax, K           ; calcular J := J + K
                mov     J, ax

É claro, se você quiser somar uma constante a uma posição de memória, você so precisa de uma instrução simples. O 80x86 deixa você adicionar diretamente uma contante para a memória:

; J := J + 2

                add     J, 2

Há formas especiais de ADD e ADc que adicionam uma constante imediata a AL, AX ou EAX. Essas formas são mais curtas que a instrução padrão ADD reg, valor imediato. Outras instruções também provêem formas mais curtas quando se usam esses registradores; portanto, você deveria tentar fazer seus cálculos com os registradores o acumulador (al, ax e eax) sempre que possível.

                add     bl, 2           ;Ocupa três bytes 
                add     al, 2           ;Dois bytes 
                add     bx, 2           ;Quatro bytes
                add     ax, 2           ;Três bytes
                etc.

Outra otimização afeta o uso de contantes pequenas com sinal com as instruções ADD e ADC. Se um valor está na escala de -128..+127, as instruções ADD e ADC vão fazer uma extensão de sinal numa constante de 8 bits imediata ao tamanho necessário para o destino (8, 16 ou 32 bits). Portanto, você deveria tentar usar contantes pequenas, se possível, com ADD e ADC.

6.5.1.2 A Instrução INC

A instrução INC adiciona 1 ao seu operando. Exceto pelo flag de carry, INC seta os flags do mesmo jeito que "ADD operando, 1" setaria.

Note que há duas formas de INC para 16 e 32 bits. São os "INC reg" e "INC reg16". O "inc reg" e o "inc mem" são a mesma coisa. Essa instrução consiste de um byte da operação seguido por um byte mod-reg-r/m (veja o Apêndice D para detalhes). A instrução INC reg16 têm só um byte de operação (opcode). Sendo assim, é mais curta e geralmente mais rápida.

O operador do INC pode ser um registrador ou posição de memória de 8, 16 ou (a partir do 80386) 32 bits.

A instrução INC é mais compacta e freqüentemente mais rápida que o comparável ADD reg, 1 ou ADD mem, 1. De fato, a instrução INC reg16 é um byte maior, então acontece que duas instruções dessas são mais curtas que o comparável ADD reg,1; contudo, as duas instruções de incremento vão rodar mais lentos na maioria dos membros modernos da família 80x86.

A instrução inc é muito importante porque adicionar 1 a um registro é uma operação muito comum. Incrementar variáveis de controle de loop ou índices em um array são operações muito comuns, perfeitas para o INC. O fato que INC não afeta o flag de carry é muito importante. Isso permite a você incrementar índices de arrays sem afetar o resultado de uma operação aritmética de multiprecisão (veja o Capítulo Nove para mais detalhes sobre aritmética multiprecisão).

6.5.1.3 A Instrução XADD

Xadd (Exchange e Add) é outra instrução 80486 (e superior). Ela não aparece no processador 80386 e mais antigos. Esta instrução adiciona o operando fonte ao destino e armazena a soma no destino. Contudo, antes de armazenar a soma, ele copia o valor original do destino dentro do operando de fonte. O seguinte algoritmo descreve essa operação:

                xadd dest, source

                temp := dest
                dest := dest + source
                source := temp

O XADD seta os flags do mesmo modo que o ADD faria. O xadd permite operandos de 8, 16 e 32 bits. Tanto o fonte quanto o destino devem ter o mesmo tamanho.

6.5.1.4 As Instruções AAA e DAA

O AAA (Ajuste ASCII após adição) e o DAA (Ajuste decimal para Adição) suportam aritmética BCD. Além desse capítulo, esse texto não vai cobrir aritmética BCD ou ASCII, já que ele é principalmente para aplicações de controle, não para aplicações de uso geral. Valores BCD são inteiros decimais codificados de forma binária com um dígito decimal (0..9) por nibble. Valores ASCII (numéricos) contêm um dígito decimal por byte, o nybble de ordem superior deve conter zero.

As instruções AAA e DAA modificam o resultado de uma adição binária para corrigi-lo para aritmética ASCII ou decimal. Por exemplo, para adicionar dois valores BCD, você os adicionaria como se eles fosse dois números binários, e executaria DAA depois, para corrigir o resultado. Do mesmo modo, você pode usar AAA para ajustar o resultado de uma adição ASCII após usar o ADD. Por favor, note que essas duas instruções assumem que os operandos no ADD eram valores corretos ASCII ou decimais. Se você adicionar valores binários (não-decimais ou não-ASCII) e tentar ajustá-los com essas instruções, você não vai produzir resultados certos.

A escolha do nome "aritmética ASCII" é infeliz, já que esses valores não são caracteres ASCII verdadeiros. Um nome como "BCD não-empacotado" seria mais apropriado. Contudo, a Intel usao o nome ASCII, então este texto vai fazer o mesmo para evitar confusão. Porém, você vai ouvir freqüentemente o termo "BCD não-empacotado" para descrever esse tipo de dados.

AAA (que você geralmente executa após um add, adc ou xadd) verifica se há overflow BCD no valor em AL. Funciona de acordo com o seguinte algoritmo:

if ( (al and 0Fh) > 9 or (AuxC =1) ) then

        if (8088 or 8086) then 
                al := al + 6
        else 
                ax := ax + 6
        endif

        ah := ah + 1
        AuxC := 1               ; Seta os flags de carry 
        Carry := 1              ; e carry auxiliar.

else

        AuxC := 0               ; Limpa os flags de carry 
        Carry := 0              ; e carry auxiliar.
endif
al := al and 0Fh

A instrução AAA é principalmente útil para adicionar strings de dígitos onde há exatamente um dígito decimal por byte na string de números. Esse texto não vai lidar com números BCD ou ASCII, então você pode ignorar seguramente essa instrução agora. É claro, você pode usar AAA a qualquer tempo que precisar de usar o algoritmo acima, mas aquilo seria uma situação rara.

A instrução DAA funciona como a AAA, exceto que ela manuseia valores BCD (binary code decima) empacotados ao invés de valores com um dígito por byte não empacotados que AAA manuseia. Como no AAA, o propósito do DAA é somar strings de dígitos BCD (com dois dígitos por byte). O algoritmo para o DAA é:

if ( (AL and 0Fh) > 9 or (AuxC = 1)) then

        al := al + 6
        AuxC := 1               ; Seta o flag de carry auxiliar.

endif
if ( (al > 9Fh) or (Carry = 1)) then

        al := al + 60h
        Carry := 1;             ; Seta o flag de carry.

endif
6.5.2 As Instruções de Subtração: SUB, SBB, DEC, AAS, e DAS

As instruções SUB (subtrair), SBB (subtrair com "pede-emprestado"), DEC (decrementar), AAS (Ajuste ASCII para Subtração), e DAS (Ajuste Decimal para Subtração) trabalham como você imagina. A sintaxe é muito similar às das instruções de adição:

                sub     reg, reg
                sub     reg, mem
                sub     mem, reg
                sub     reg, dado imediato
                sub     mem, dado imediato
                sub     eax/ax/al, dado imediato

                as formas de sbb são idênticas às de sub.

                dec     reg
                dec     mem
                dec     reg16
                aas
                das

A instrução SUB computa o valor dest := dest - fonte. O SBB calcula dest : dest - fonte - C. Note que a subtração não é comutativa. Se você quiser calcular o resultado para dest := dest - fonte, vai precisar de usar muitas instruções (asumindo que você queira preservar o operando fonte).

Um último assunto que vale a pena discutir é como o SUB afeta os flags do 80x86. Os sub, sbb e dec afetam o flag desse modo:

A instrução AAS, bem como sua contraparte AAA, deixa você operar em strings de números ASCII com um dígito decimal (na escala de 0..9) por byte. Você usaria essa instrução após um SUB no valor ASCII. Essa instrução usa o seguinte algoritmo:

if ( (al and 0Fh) > 9 or AuxC = 1) then
        al := al - 6
        ah := ah - 1
        AuxC := 1       ; Seta o flag de carry auxiliar
        Carry := 1      ; e o de carry.
else
        AuxC := 0       ; Limpa o flag de carry auxiliar
        Carry := 0      ; e o de carry.
endif
al := al and 0Fh

A instrução DAS manuseia a mesma operação para valores BCD, ela usa o seguinte algoritmo:

if ( (al and 0Fh) > 9 or (AuxC = 1)) then
        al := al -6
        AuxC = 1
endif
if (al > 9Fh or Carry = 1) then
        al := al - 60h
        Carry := 1              ; Seta o carry de flag.
endif

Já que a subtração não é comutativa, você não pode usar o SUB tão livremente quanto o ADD. Os seguintes exemplos demonstram alguns problemas que você pode encontrar.

; J := K - J

                mov     ax, K           ; Esta é uma boa tentativa, mas ela calcula
                sub     J, ax           ; J := J - K, a subtração não é
                                        ; comutativa!

                mov     ax, K           ; Solução correta.
                sub     ax, J
                mov     J, ax

; J := J - (K + M) -- Não se esqueça que isso é equivalente a J := J - K - M

                mov     ax, K           ; Computa AX := K + M
                add     ax, M
                sub     J, ax           ; Computa J := J - (K + M)

                mov     ax, J           ; Outra solução, só que 
                sub     ax, K           ; menos eficiente
                sub     ax, M
                mov     J, ax

Note que as instruções sub e sbb, assim como as add e adc, provêem formas curtas para subtrair uma constante de um registrador acumulador (al, ax ou eax). Por essa razão, você deveria tentar fazer operações aritméticas nos acumuladores tanto quanto possível. As instruções sub e sbb também provêem uma forma mais curta quando se subtrai valores entre -127 e +128 de uma posição de memória ou de um registrador. A instrução vai automaticamente estender o sinal de um valor de 8 bits para o tamanho necessário antes de executar a subtração. Veja o Apêndice D para mais detalhes.

Na prática, não há necessidade para uma instrução que subtrai uma constante de um registrador ou de uma posição de memória - adicionar um valor negativo dá no mesmo resultado. Mesmo assim, a Intel provê uma instrução para subtração imediata.

Após a execução de um SUB, os bits de condição (carry, sinal, overflow e zero) no registrador de flags contêm valores que você pode testar para ver se um dos operandos do SUB é igual, diferente, menor que, menor ou igual, maior, ou maior ou igual que o outro operando. Veja a instrução CMP para mais detalhes.

6.5.3 A Instrução CMP

A instrução CMP (compare) é idêntica a um SUB, com uma diferença crucial - ela não armazena a diferença no operando destino. A sintaxe para a CMP é muito similar ao SUB, a forma genérica é

                cmp     dest, src

As formas específicas são

                cmp     reg, reg
                cmp     reg, mem
                cmp     mem, reg
                cmp     reg, dado imediato
                cmp     mem, dado imediato
                cmp     eax/ax/al, dado imediato

A CMP atualiza os flags do 80x86 de acordo com o resultado da subtração (destino - fonte). Você pode testar o resultado da comparação checando os flags apropriados no registrador de flags. Para detalhes sobre como isso é feito, veja "As Instruções de Set para Condição" e "As Instruções de Jump Condicionais".

Geralmente você vai querer executar uma instrução de jump condicional depois de um CMP. Esse processo de dois passos, comparar dois valores e setar os bits de flag e então testar os flags com as instruções de jump condicional, é um mecanismo muito eficiente para fazer decisões em um programa.

Provavelmente o primeiro lugar para se começar quando explorar a instrução CMP é dar uma olhada em como a instrução afeta exatamente os flags. Considere a seguinte instrução CMP:

		cmp	ax, bx

Essa instrução realiza o cálculo AX-BX e seta os flags dependendo do resultado da conta. Os flags são setados de acordo como se segue:

Z: O flag de zero é setado se e somente se AX=BX. Esse é o único momento em que AX-BX produz zero no resultado. Daí, você pode usar o flag de zero para testar igualdade ou desigualdade.

S: O flag de sinal é setado se o resultado for negativo. À primeira vista, você poderia pensar que esse flag seria setado se AX fosse menor que BX, mas isso não é sempre o caso. Se AX=7FFFh e BX=-1(0FFFFh), subtrair Ax de BX produz 8000h, que é negativo (e então o flag de sinal será setado). Então, para comparações com sinal, de qualquer modo, o flag de sinal não contém o status certo. para operandos sem sinal, considerando AX=0FFFh e BX=1, AX é maior que BX, mas sua diferença é 0FFFEh, que ainda é negativo. Assim, o flag de sinal e o de overflow, juntos, podem ser usados para comparar dois valores com sinal.

O: O flag de overflow é setado após um CMP se a diferença de Ax e BX produzir um overflow ou um underflow. Como mencionado acima, o flag de sinal e o de overflow são ambos usados quando se realiza comparações com sinal.

C: O flag de carry é setado após um CMP se a subtraindo BX de AX requer um "pede-emprestado". Isso só ocorre se Ax for menor que BX, quando AX e BX são valores sem sinal.

O CMP também afeta os flags de paridade e carry auxiliar, mas você raramente vai testá-los depois de uma comparação. Dado que o CMP seta os flags desse modo, você pode testar a comparação dos dois operandos com os seguintes flags:

		cmp Oprnd1, Oprnd2
Condition Code Settings After CMP
Operandos sem sinal: Operandos com sinal:
Z: igualdade/desigualdade Z: igualdade/desigualdade
C: Oprnd1 < Oprnd2 (C=1), Oprnd1 >= Oprnd2 (C=0) C: sem siginificado
S: sem siginificado S: veja abaixo
O: sem siginificado O: veja abaixo

Para comparações com sinal, os flags S (sinal) e O (overflow), tomados juntos, têm o seguinte significado:

Se ((S=0) e (O=1)) ou ((S=1) e (O=0)) então Oprnd1 < Oprnd2 quando se usa uma comparação com sinal.

Se ((S=0) e (O=0)) ou ((S=1) e (O=1)) então Oprnd1 >= Oprnd2 quando se usa uma comparação com sinal.

Para entender por que esses flags são setados dessa maneira, considere os seguintes exemplos:

        Oprnd1        menos     Oprnd2          S       O
        ------                  ------          -       -

        0FFFF (-1)      -       0FFFE (-2)      0       0
        08000           -       00001           0       1
        0FFFE (-2)      -       0FFFF (-1)      1       0
        07FFF (32767)   -       0FFFF (-1)      1       1

Lembre, o CMP é, na verdade, uma subtração, então, o primeiro exemplo calcula (-1) - (-2), que é (+1). O resultado é positivo e um overflow não ocorreu, então tanto S quanto O são zero. Já que (S xor O) é zero, Oprnd1 é maior ou igual a Oprnd2.

No segundo exemplo, a instrução CMP calcularia (-32768)-(+1), que é (-32769). Já que um inteiro de 16 bits com sinal não pode representar esse valor, o valor é arredondado para 7FFFh (+32767) e seta o flag de overflow. E como o resultado é positivo (pelo menos dentro dos 16 bits) o flag de sinal é zerado. E desde que (S xor O) é 1 aqui, Oprnd1 é menor que Oprnd2.

No terceiro exemplo acima, CMP computa (-2)-(-1), que produz (-1). nenhum overflow ocorreu, então o flag O é zero, e o resultado é negativo, então o flag de sinal é 1. Já que (S xor O) é 1, Oprnd1 é menos que Oprnd2.

No quarto (e último) exemplo, CMP computa (+32767)-(-1). Isso produz (+32768), setando o flag de overflow. Além disso, o valor arredonda para 8000h (-32768) de modo que o flag de sinal também é setado. Já que (s xor O) é zero, Oprnd1 é maior ou igual a Oprnd2.

6.5.4 As Instruções CMPXCHG, e CMPXCHG8B

O instrução CMPXCHG (compara e troca) está disponível apenas a partir do 80486. Ela surporta a seguinte sintaxe:

                cmpxchg reg, reg
                cmpxchg mem, reg

Os operandos devem ser do mesmo tamanho (8, 16, ou 32 bits). Esta instrução também usa o registrador acumulador; ela escolhe automaticamente AL, AX ou EAX de acordo com o tamanho dos operandos.

Esta instrução compara AL, AX ou EAX com o primeiro operando e seta o flag de zero se forem iguais. Se for assim, então a CMPXCHG copia o segundo operando para o primeiro. Se não forem iguais, ela copia o primeiro operando para o acumulador. O seguinte algoritmo descreve esta operação:

        cmpxchg         operand1, operand2

        if ({al/ax/eax} = operand1) then

                zero := 1               ; Seta o flag de zero
                operand1 := operand2

        else

                zero := 0               ; Limpa o flag de zero
                {al/ax/eax} := operand1

        endif

CMPXCHG suporta certas estruturas de dados do sistema operacional requerendo operações atômicas e semáforos. É clarom se você puder encaixar o algoritmo acima no seu código, pode usar o CMPXCHG como apropriado.

Note: diferentemente do CMP, o CMPXCHG só afeta o flag de zero. Você não pode testar os outros flags depois dessa instrução, como poderia com o CMP.

O processador Pentium suporta uma instrução de comparação e troca de 64 bits - CMPXCHG8B. Ela usa a sintaxe:

                cmpxchg8b ax, mem64

Essa instrução compara o valor de 64bits em EDX:EAX com o valor da memória. Se forem iguais, o Pentium armazena ECX:EBX na posição de memória, senão ele carrega EDX:EAX com a posição da memória. Essa instrução seta o flag de zero de acordo com o resultado. Não afeta nenhum outro flag.

6.5.5 A Instrução NEG

A instrução NEG (negação) faz o complemento a dois de um byte ou word. Ela receve apenas um operando (destino) e o nega. A sintaxe para essa instrução é

		neg	dest

Ela computa o seguinte:

		dest := 0 - dest

Isso inverte efetivamente o sinal do operando destino.

Se o operando for zero, seu sinal não muda, embora isso limpe o flag de carry. Negar qualquer outro valor seta o flag de carry. Negar um byt contendo -128, uma word contendo -32.768, ou uma double word contendo -2.147.483.648 não muda o operando, mas seta o flag de overflow. NEG sempre atualiza os flags A, S, P e Z bem como se você estivesse usando a instrução SUB.

As fomas permitidas são:

                neg     reg
                neg     mem

Os operandos podem ser valores de 8, 16 ou 32 (a partir do 80386) bits.

Alguns exemplos:

; J := - J

                neg     J

; J := -K
                mov     ax, K
                neg     ax
                mov     J, ax

6.5.6 As Instruções de Multiplicação: MUL, IMUL, e AAM

As instruções de multipicação provêm você com seu primeiro gostinho de irregularidade no conjunto de instruções do 8086. Instruções como ADD, ADC, SBB, e muitas outras do conjunto de instruções do 8086 usam um byte mod-reg-r/m para suportar dois operandos. Infelizmente, não há bits suficientes nos bytes de operação do 8086 para suportar todas as instruções, então o 8086 usa os bits de registro no byte de mod-reg-r/m como extensão aos 8 bits com o código da operação. Por exemplo, INC, DEC e NEG não requerem dois operandos, então as CPUs 80x86 usam os bits reg como extensão ao código da operação de 8 bits. Isso funciona bem para instruções de apenas um operando, permitindo aos projetistas da Intel codificarem várias instruções (oito, na verdade) com um opcode só.

Infelizmente, as instruções de multiplicação requerem tratamento especial e os projetistas da Intel estavam em falta de opcodes, então eles projetaram as instruções de multiplicação de modo a usar só um operando. O campo reg contém uma extensão ao opcode ao invés de um valor. É claro que a multiplicação é uma operação com dois operandos. O 8086 sempre assume que o acumulador (AL, AX, ou EAX) é o operando destino. Essa irregularidade faz o uso da multiplicação no 8086 um pouco mais difícil que as outras instruções, porque um operando tem que estar no acumulador. A Intel adotou essa idéia não-ortogonal porque eles acharam que os programadores usariam a multiplicação bem menos freqüentemente que instruções como ADD e SUB.

Um problema em prover apenas uma forma mod-reg-r/m da instrução é que você não pode multiplicar o acumulador por uma constante; o byte mod-reg-r/m não suporta o endereçamento imediato. A Intel descobriu rapidamente a necessidade de dar suporte à multiplicação por uma constante e provê suporte a isso no procesador 80286. Isso era importante espcialmente para acessos a arrays multidimensionais. Pela época em que o 80386 saiu, a Intel generalizou uma forma de operação de multiplicação, permitindo operandos mod-reg-r/m padrão.

Há duas formas da instrução de multiplicação: uma multiplicação sem sinal (MUL) e uma com sinal (IMUL). Diferentemente da adição e da subtração, você precisa de instruções separadas para essas duas operações.

As instruções de multiplicação levas as seguintes formas:

Multiplicação sem sinal:

                mul     reg
                mul     mem

Multiplicação (Inteira) com sinal:

                imul    reg
                imul    mem
                imul    reg, reg, immediate     (2)
                imul    reg, mem, immediate     (2)
                imul    reg, immediate          (2)
                imul    reg, reg                (3)
                imul    reg, mem                (3)

Operação de Multiplicação BCD:

                aam

2- Disponível no 80286 e superior, somente.
3- Disponível no 80386 e superior, somente.

Como você pode ver, as instruções de multiplicação são uma verdadeira bagunça. Pior ainda, você precisa ter um 80386 ou superior para ter perto da funcionalidade completa. Finalmente, há algumas restrições nessas instruções não óbvias acima. O único jeito de lidar com essas instruções é memorizar sua operação.

MUL, disponível em todos os processadores, multiplica um operando sem sinal, de 8, 16 ou 32 bits. Note que quando se multiplica dois valores de n bits, o resultado pode requerer 2*n bits. Portanto, se o operando tem 8 bits, o resultado requer 16 bits. Semelhantemente, um operando de 16 bits produz um resultado com 32 e um operando de 32 bits requer 64 bits para o resultado.

A instrução MUL, com operando de 8 bits, multiplica o registrador AL pelo operando e armazena o resultado de 16 bits em AX. Então,

		mul	operando8
ou		imul	operando8

computa:

		ax := al * operando8

"*" representa uma multiplicação sem sinal por MUL e uma multiplicação com sinal por IMUL.

Se você especificar um operando de 16 bits, então MUl e IMUL calculam:

		dx:ax := ax * operando16

"*" tem o mesmo significado como acima, e DX:AX significa que DX contém a word de ordem superior do resultado de 32 bits e AX contém a word de ordem baixa do resultado de 32 bits.

Se você especificar um operando de 32 bits, então MUl e IMUL calculam:

		edx:eax := eax * operand32

"*" tem o mesmo significado como acima, e EDX:EAX significa que EDX contém a double word de ordem superior do resultado de 64 bits e EAX contém a double word de ordem baixa do resultado de 64 bits.

Se um produto de 8x8 bits, 16x16 bits ou 32x32 bits requerer mais que oito, dezesseis ou treita e dois bits (respectivamente), as instruções mul e imul setam os flags de carry e de overflow.

Mul e imul bagunçam os flags A, P, S, e Z. Especialmente, note que os flags de sinal e de zero não contêm valor significativo após a execução dessas duas intruções.

Imul (multiplicação inteira) opera com operandos com sinal. Há muitas formas diferentes dessa instrução já que a Intel tentou generalizar essa instrução com processadores sucessivos. Os parágrafos anteriores descrevem a promeira forma do imul, com apenas um operando. As próximas três formas de imul estão disponíveis somente no 80286 e superiores. Elas provêem a habilidade de se multiplicar um registrador por um valor imediato. As últimas duas formas, disponíveis somente a partir do 80386, provêem a capacidade de se multiplicar um registrador arbitrário por outro registrador ou posição de memória. Expadidas para mostrar os tamnahos permitidos dos operandos, eslas são:

        imul    operand1, operand2, immediate   ;forma geral

        imul    reg16, reg16, immediate8
        imul    reg16, reg16, immediate16
        imul    reg16, mem16, immediate8
        imul    reg16, mem16, immediate16
        imul    reg16, immediate8 
        imul    reg16, immediate16
        imul    reg32, reg32, immediate8        (3)
        imul    reg32, reg32, immediate32       (3)
        imul    reg32, mem32, immediate8        (3)
        imul    reg32, mem32, immediate32       (3)
        imul    reg32, immediate8               (3)
        imul    reg32, immediate32              (3)

3- Disponíveis somente a partir do 80386.

As instruções imul reg, immediate são uma sintaxe especial que o assembler provê. A codificação para essas instruções são as mesma de imul reg, reg, immediate. O assembler simplesmente coloca o mesmo valor do registrador para ambos os operandos.

Essas instruções computam:

	operand1 := operand2 * immediate
	operand1 := operand1 * immediate

Além donúmero de operandos, há muitas diferenças entre essas formas e as intruções simples mul/imul:

As últimas duas formas do imul estão disponíveis somente no 80386 e superiores. Com a adição desses formatos, a instrução imul é quase tão geral quanto a instrução add:

		imul	reg, reg
		imul	reg, mem

Essas instruções calculam

		reg := reg * reg
e		reg := reg * mem

Ambos os operandos devem ser do mesmo tamanho. Portanto, como na forma do 80286 para o imul, você deve testar os flags de carry ou de overflow para detectar overflows. Se isso ocorrer, a CPU perde os bits mais altos do resultado.

Nota Importante: Tenha em mente que o flag de zero contém um resultado indeterminado após a instrução de multiplicação. Você não pode testar o flag de zero para ver o resultado após uma multiplicação. Semelhantemente, essas instruções bagunçam o flag de sinal. Se você precisa de testar esses flags, compare o resultado com zero após testar os flags de carry ou de overflow.

A instrução aam (ASCII Adjust after Multiplication / Ajustte ASCII após Multiplicação), como o aaa e o aas, deixa você ajustar um valor decimal não-empacotado após a multiplicação. Essa instrução opera diretamente no AX. Ela assume que você multiplicou dois valores de 8 bits na escala 0..9 e o resultado está em AX (na verdade, o resultado estará em AL, já que 9*9 é 81, o maior valor possível; AH vale zero). Essa instrução divide AX por 10 e deixa o quociente em AH e o resto em AL:

		ah := ax div 10
		al := ax mod 10

Diferente de outras instruções de ajuste decimal/ASCII, programas em linguagem assembly regularmente usam o aam já que a conversão entre bases numéricas usam esse algoritmo.

Nota: a instrução aam consiste de um opcode de dois bytes, o segundo byte dele é a constante imediata 10. Programadores de assembly descobriram que se você substituir outro valor por essa constante, você pode mudar o divisor no algoritmo acima. Isso, contudo, é uma característica não documentada. Funciona em todos os processadores que a Intel produziu até agora, mas não há garantias de que a Intel vai dar suporte a isso em processadores futuros. É claro, os processadores 80286 e superiores deixam você multiplicar por uma constante, então esse truque é quase desnecessário em sistemas modernos.

Não existe um dam (ajuste decimal para multiplicação) para o processador 80x86

Talvez o uso mais comum da instrução imul seja computar deslocamentos em arrays multidimensionais. De fato, essa é provavelmente a razão principal pela qual a Intel tenha adicionado a capacidade de se multiplicar um registrador por uma constante no processador 80286. No Capítulo 4, esse texto usou a instrução padrão do 8086 mul para cálculos de índice de arrays. Porém, a sintaxe estendida do imul é uma escolha muito melhor, como o seguinte exemplo demosntra:

MyArray         word    8 dup ( 7 dup ( 6 dup (?)))                             ;8x7x6 array.
J               word    ?
K               word    ?
M               word    ?
                 .
                 .
                 .
; MyArray [J, K, M] := J + K - M

                mov     ax, J
                add     ax, K
                sub     ax, M

                mov     bx, J           ;Array index :=
                imul    bx, 7           ;       ((J*7 + K) * 6 + M) * 2
                add     bx, K
                imul    bx, 6
                add     bx, M
                add     bx, bx          ;BX := BX * 2

                mov     MyArray[bx], ax

Não se esqueça de que as instruções de multiplicação são muito lentas; freqüentemente uma ordem de magnitude mais lentas que a instrução de adição. Há modos mais rápidos de multiplicar um valor por uma constante. Veja o Capítulo 9 para todos os detalhes.

6.5.7 As Instruções de Divisão: DIV, IDIV, e AAD

As instruções de divisão do 80x86 realizam uma divisão de 64/32 (80386 and later only), uma de 32/16 ou uma divisão de 16/8. Essas instruções têm a forma:

                div     reg     Para divisão sem sinal
                div     mem

                idiv    reg     Para divisão com sinal
                idiv    mem

                aad             Ajuste ASCII para divisão

A instrução div calcula uma divisão sem sinal. Se o operando for de oito bits, div divide o AX pelo operando, deixando o quociente em AL e o resto (modulo) em AH. Se o operando for de 16 bits, a nstrução div divide o número de 32 bits formado por DX:AX pelo operando, ficando o quociente em AX e o resto em DX. Com operandos de 32 bits (80386 e superiores), o div divide o valor de 64 bits que está em EDX:EAX pelo operando, ficando o quociente em EAX e o resto em EDX.

Você não pode, no 80x86, dividir um valor de 8 bits por outro. Se o denominador for um valor de 8 bits, o numerador deve ter 16 bits. Se você precisar dividir um valor de 8 bits sem sinal por outro, você deve estender com zeros o numerador para caber em 16 bits. Você pode fazer isso carregando o numerador em AL e movendo o valor zero para AH. Então você poderá dividir Ax pelo operando denominador para produzir o resultado correto. Falhas em estender com zeros antes de executar o DIV pode causar resultados incorretos no 80x86!

Quando você precisar dividir dois valores sem sinal de 16 bits, você deve estender com zeros o AX (que contém o numerador) em DX. Basta carregar o valor 0 em DX. Se precisar dividir um valor de 32 bits por outro, estenda com zeros EAX em EDX (carregando o valor de EDX com 0) antes da divisão.

Quando for lidar com valores inteiros com sinal, você vai precisar estender o sinal de AL para AX, AX para DX ou EAX para EDX, antes de executar o IDIV. Para fazer isso, use as instruções cbw, cwd, cdq, ou movsx. Se o bit ou word de ordem alta já não contiver bits significantes, então você deve estender com sinal o valor no acumulador (AL/AX/EAX) antes de executar o IDIV. Falhas em fazer isso podem produzir resultados incorretos.

Existe uma outr apegadinha nas instruções de divisão do 80x86: você pode obter um erro fatal quando usar essa instrução. Primeiro, é claro, você pode tentar dividir um valor por zero. Além disso, o quociente pode ser muito grande para caber em EAX, AX ou AL. Por exemplo, a divisão 16/8 dos valores "8000h / 2" produz o quociente 4000h, com resto zero. 4000h não caberá nos oito bits. Se isso acontecer, ou se você tentar dividir por zero, o 80x86 vai gerar um int 0. Isso geralmente significa que a BIOS vai imprimir "division by zero" ou "divide error" e abortar seu programa. Se isso acontecer a você, as chances são de que você não estendeu o sinal ou em zeros o seu numerador antes de executar a operação de divisão. Já que esse erro vai fazer seu programa travar, você deveria ter muito cuidado com os valores que vai selecionar quando for dividir.

Os flags de carry auxiliar, de carry, de overflow, de paridade, de sinal e de zero são indefinidos após uma divisão. Se um overflow acontecer (ou se você tentar uma divisão por zero) então o 80x86 executar uma INT 0 (interrupção zero).

Note que o 80286e processadores mais novos não têm formas especiais para o idiv como têm para o imul. A maioria dos programas usa a divisão com bem menor freqüência que a multiplicação, então os designers da Intel não se preocuparam em cria instruções especiais para a divisão. Note que não há um modo de se dividir por um valor imediato. Você tem que carregar o valor num registrador ou posição de memória e fazer a divisão naquele registrador ou posição de memória.

O aad (ASCII Adjust before Division) é outra operação de decimais não empacotados. Ele separa valores decimais codificados em binário antes de realizar divisões ASCII. Embora esse texto não vá cobrir aritmética BCD, a instrução AAD é util para outras operações. O algoritmo que descreve a instrução é

	al := ah*10 + al
	ah := 0

A instrução é bastante útil para converter strings de dígitos em valores inteiros (veja as questões no fim desse capítulo).

Os seguintes exempls mostram como dividir um valor de 16 bits por outro.

; J := K / M (sem sinal)

                mov     ax, K   ;pega o dividendo
                mov     dx, 0   ;estende em zeros o valor sem sinal em AX para DX.

        < Na prática, deveríamos verificar que M não contenha zero aqui >

                div     M
                mov     J, ax

; J := K / M (com sinal)

                mov     ax, K   ;Pega o dividendo
                cwd             ;estende o sinal do valor com sinal em AX para DX.

        < Na prática, deveríamos verificar que M não contenha zero aqui>

                idiv    M
                mov     J, ax

; J := (K*M)/P

                mov     ax, K   ;Note que o imul produz
                imul    M       ; um resultado de 32 bitsem DX:AX, então 
                idiv    P       ; não precisamos de estender o sinal de AX aqui.
                mov     J, ax   ;Espere e reze para que o resultado caiba em 16 bits!

Chapter Six (Part 1)

Table of Content

Chapter Six (Part 3)

Chapter Six: The 80x86 Instruction Set (Part 2)
26 SEP 1996
Tradução: Renato Nunes Bastos
15/Out/2004