Menu English Ukrainian Russo Início

Biblioteca técnica gratuita para amadores e profissionais Biblioteca técnica gratuita


Ciência da computação e tecnologia da informação. Comandos de transferência de controle (notas de aula)

Notas de aula, folhas de dicas

Diretório / Notas de aula, folhas de dicas

Comentários do artigo Comentários do artigo

Índice (expandir)

PALESTRA Nº 18. Equipes

1. Comandos de transferência de dados

Por conveniência de aplicação prática e reflexão de suas especificidades, é mais conveniente considerar os comandos deste grupo de acordo com sua finalidade funcional, segundo o qual podem ser divididos nos seguintes grupos de comandos:

1) transferências de dados de uso geral;

2) entrada-saída para a porta;

3) trabalhar com endereços e ponteiros;

4) transformações de dados;

5) trabalhe com a pilha.

Comandos gerais de transferência de dados

Este grupo inclui os seguintes comandos:

1) mov é o comando básico de transferência de dados. Ele implementa uma ampla variedade de opções de envio. Observe as especificidades deste comando:

a) o comando mov não pode transferir de uma área de memória para outra. Se tal necessidade surgir, então qualquer registrador de propósito geral atualmente disponível deve ser usado como um buffer intermediário;

b) é impossível carregar um valor diretamente da memória em um registrador de segmento. Portanto, para executar essa carga, você precisa usar um objeto intermediário. Este pode ser um registrador de uso geral ou uma pilha;

c) você não pode transferir o conteúdo de um registrador de segmento para outro registrador de segmento. Isso ocorre porque não há opcode correspondente no sistema de comando. Mas muitas vezes surge a necessidade de tal ação. Você pode realizar tal transferência usando os mesmos registradores de propósito geral que os intermediários;

d) você não pode usar o registrador de segmento CS como operando de destino. A razão é simples. O fato é que na arquitetura do microprocessador, o par cs:ip sempre contém o endereço do comando que deve ser executado em seguida. Alterar o conteúdo do registrador CS com o comando mov significaria na verdade uma operação de salto, não uma transferência, o que é inaceitável. 2) xchg - usado para transferência de dados bidirecional. Para esta operação, é claro, você pode usar uma sequência de várias instruções mov, mas devido ao fato de a operação de troca ser usada com bastante frequência, os desenvolvedores do sistema de instruções do microprocessador consideraram necessário introduzir uma instrução de troca xchg separada. Naturalmente, os operandos devem ser do mesmo tipo. Não é permitido (como em todas as instruções do montador) trocar o conteúdo de duas células de memória entre si.

Comandos de E/S da porta

Observe a Figura 22. Ela mostra um diagrama conceitual altamente simplificado de controle de hardware de computador.

Arroz. 22. Diagrama conceitual de controle de hardware de computador

Como você pode ver na Figura 22, o nível mais baixo é o nível do BIOS, onde o hardware é manipulado diretamente pelas portas. Isso implementa o conceito de independência do equipamento. Ao substituir o hardware, será necessário apenas corrigir as funções correspondentes do BIOS, reorientando-as para novos endereços e a lógica das portas.

Em princípio, é fácil gerenciar dispositivos diretamente por meio de portas. Informações sobre números de porta, sua profundidade de bits, formato de informações de controle são fornecidas na descrição técnica do dispositivo. Você só precisa saber o objetivo final de suas ações, o algoritmo de acordo com o qual um determinado dispositivo funciona e a ordem de programação de suas portas, ou seja, de fato, você precisa saber o que e em qual sequência você precisa enviar para a porta (ao escrever para ela) ou ler a partir dela (ao ler) e como essa informação deve ser interpretada. Para isso, bastam dois comandos presentes no sistema de comandos do microprocessador:

1) no acumulador, port_number - entrada no acumulador da porta com o número port_number;

2) porta de saída, acumulador - envia o conteúdo do acumulador para a porta com o número port_number.

Comandos para trabalhar com endereços e ponteiros de memória

Ao escrever programas em assembler, é feito um trabalho intensivo com os endereços dos operandos que estão na memória. Para suportar este tipo de operações, existe um grupo especial de comandos, que inclui os seguintes comandos:

1) lea destino, origem - carregamento de endereço efetivo;

2) Ids destino, origem - carregando o ponteiro no registrador de segmento de dados ds;

3) les destination, source - carregando o ponteiro no registro dos segmentos de dados adicionais;

4) destino lgs, origem - carregando o ponteiro no registro do segmento de dados adicional gs;

5) lfs destino, origem - carregando o ponteiro no registro do segmento de dados adicional fs;

6) destino lss, fonte - ponteiro de carregamento no registrador de segmento de pilha ss.

O comando lea é semelhante ao comando mov, pois também executa um movimento. Entretanto, a instrução lea não transfere dados, mas sim o endereço efetivo dos dados (ou seja, o deslocamento dos dados desde o início do segmento de dados) para o registrador indicado pelo operando destino.

Muitas vezes, para realizar alguma ação em um programa, não basta saber apenas o valor do endereço efetivo dos dados, mas é necessário ter um ponteiro completo para os dados. Um ponteiro de dados completo consiste em um componente de segmento e um deslocamento. Todos os outros comandos deste grupo permitem que você obtenha um ponteiro completo para um operando na memória em um par de registradores. Neste caso, o nome do registrador de segmento, no qual o componente de segmento do endereço é colocado, é determinado pelo código de operação. Assim, o deslocamento é colocado no registro geral indicado pelo operando de destino.

Mas nem tudo é tão simples com o operando fonte. De fato, no comando como fonte, você não pode especificar diretamente o nome do operando na memória, para o qual gostaríamos de receber um ponteiro. Primeiro, você precisa obter o valor do ponteiro completo em alguma área da memória e especificar o endereço completo do nome dessa área no comando get. Para executar esta ação, você precisa lembrar as diretivas para reservar e inicializar a memória.

Ao aplicar essas diretivas, um caso especial é possível quando o nome de outra diretiva de definição de dados (na verdade, o nome de uma variável) é especificado no campo do operando. Neste caso, o endereço desta variável é formado na memória. Qual endereço será gerado (efetivo ou completo) depende da diretiva aplicada. Se for dw, apenas o valor de 16 bits do endereço efetivo é formado na memória; se for dd, o endereço completo é gravado na memória. A localização desse endereço na memória é a seguinte: a palavra baixa contém o deslocamento, a palavra alta contém o componente de segmento de 16 bits do endereço.

Por exemplo, ao organizar o trabalho com uma cadeia de caracteres, é conveniente colocar seu endereço inicial em um determinado registro e depois modificar esse valor em um loop para acesso sequencial aos elementos da cadeia.

A necessidade de usar comandos para obter um ponteiro de dados completo na memória, ou seja, o endereço do segmento e o valor de deslocamento dentro do segmento, surge, em particular, quando se trabalha com cadeias.

Comandos de conversão de dados

Muitas instruções de microprocessadores podem ser atribuídas a este grupo, mas a maioria delas possui certas características que requerem que sejam atribuídas a outros grupos funcionais. Portanto, de todo o conjunto de comandos do microprocessador, apenas um comando pode ser atribuído diretamente aos comandos de conversão de dados: xlat [address_of_transcoding_table]

Esta é uma equipe muito interessante e útil. Seu efeito é que ele substitui o valor no registrador al por outro byte da tabela de memória localizada no endereço especificado pelo operando remap_table_address.

A palavra "tabela" é muito condicional, na verdade, é apenas uma sequência de bytes. O endereço do byte na string que substituirá o conteúdo do registrador al é determinado pela soma (bx) + (al), ou seja, o conteúdo de al atua como um índice no array de bytes.

Ao trabalhar com o comando xlat, preste atenção ao seguinte ponto sutil. Mesmo que o comando especifique o endereço da cadeia de bytes da qual o novo valor deve ser recuperado, esse endereço deve ser pré-carregado (por exemplo, usando o comando lea) no registrador bx. Assim, o operando lookup_table_address não é realmente necessário (a opcionalidade do operando é mostrada colocando-o entre colchetes). Quanto à cadeia de bytes (tabela de transcodificação), é uma área de memória de 1 a 255 bytes de tamanho (o intervalo de um número sem sinal em um registrador de 8 bits).

Comandos de pilha

Este grupo é um conjunto de comandos especializados focados em organizar o trabalho flexível e eficiente com a pilha.

A pilha é uma área de memória especialmente alocada para armazenamento temporário de dados do programa. A importância da pilha é determinada pelo fato de que um segmento separado é fornecido para ela na estrutura do programa. Caso o programador tenha esquecido de declarar um segmento de pilha em seu programa, o vinculador tlink emitirá uma mensagem de aviso.

A pilha tem três registradores:

1) ss - registrador de segmento de pilha;

2) sp/esp - registrador de ponteiro de pilha;

3) bp/ebp - registrador de ponteiro de base de quadro de pilha.

O tamanho da pilha depende do modo de operação do microprocessador e é limitado a 64 KB (ou 4 GB no modo protegido).

Apenas uma pilha está disponível por vez, cujo endereço de segmento está contido no registrador SS. Essa pilha é chamada de pilha atual. Para fazer referência a outra pilha ("switch the stack"), é necessário carregar outro endereço no registrador ss. O registrador SS é usado automaticamente pelo processador para executar todas as instruções que funcionam na pilha.

Listamos mais alguns recursos para trabalhar com a pilha:

1) a escrita e a leitura de dados na pilha são realizadas de acordo com o princípio LIFO,

2) à medida que os dados são gravados na pilha, esta cresce em direção aos endereços mais baixos. Esse recurso está embutido no algoritmo de comandos para trabalhar com a pilha;

3) ao utilizar os registradores esp/sp e ebp/bp para endereçamento de memória, o montador considera automaticamente que os valores nele contidos são offsets relativos ao registrador do segmento ss.

Em geral, a pilha é organizada conforme mostrado na Figura 23.

Arroz. 23. Diagrama conceitual de organização da pilha

Os registradores SS, ESP/SP e EUR/BP são projetados para trabalhar com a pilha. Esses registradores são usados ​​de forma complexa, e cada um deles tem sua própria função funcional.

O registrador ESP/SP sempre aponta para o topo da pilha, ou seja, contém o deslocamento em que o último elemento foi colocado na pilha. As instruções da pilha alteram implicitamente esse registro para que ele sempre aponte para o último elemento inserido na pilha. Se a pilha estiver vazia, o valor de esp será igual ao endereço do último byte do segmento alocado para a pilha. Quando um elemento é colocado na pilha, o processador diminui o valor do registrador esp e, em seguida, grava o elemento no endereço do novo vértice. Ao retirar os dados da pilha, o processador copia o elemento localizado no endereço do vértice e então incrementa o registrador do ponteiro da pilha esp. Assim, verifica-se que a pilha cresce para baixo, na direção de endereços decrescentes.

E se precisarmos acessar elementos não no topo, mas dentro da pilha? Para fazer isso, use o registrador EBP O registrador EBP é o registrador do ponteiro base do quadro de pilha.

Por exemplo, um truque típico ao entrar em uma sub-rotina é passar os parâmetros desejados empurrando-os para a pilha. Se a sub-rotina também estiver trabalhando ativamente com a pilha, o acesso a esses parâmetros se tornará problemático. A saída é salvar o endereço do topo da pilha no ponteiro do quadro (base) da pilha depois de escrever os dados necessários na pilha - o registrador EUR. O valor em EUR pode ser usado posteriormente para acessar os parâmetros passados.

O início da pilha está localizado em endereços de memória mais altos. Na Figura 23, este endereço é denotado pelo par ss: fffF. O deslocamento de wT é dado aqui condicionalmente. Na realidade, esse valor é determinado pelo valor que o programador especifica ao descrever o segmento da pilha em seu programa.

Para organizar o trabalho com a pilha, existem comandos especiais para escrita e leitura.

1. push source - gravando o valor da fonte no topo da pilha.

De interesse é o algoritmo deste comando, que inclui as seguintes ações (Fig. 24):

1) (sp) = (sp) - 2; o valor de sp é reduzido em 2;

2) o valor da fonte é escrito no endereço especificado pelo par ss:sp.

Arroz. 24. Como funciona o comando push

2. atribuição de pop - escrever o valor do topo da pilha para o local especificado pelo operando de destino. O valor é assim "removido" do topo da pilha. O algoritmo do comando pop é o inverso do algoritmo do comando push (Fig. 25):

1) escrever o conteúdo do topo da pilha no local indicado pelo operando destino;

2) (sp) = (sp) + 2; aumentando o valor de sp.

Arroz. 25. Como funciona o comando pop

3. pusha - um comando de gravação de grupo na pilha. Por este comando, os registradores ax, cx, dx, bx, sp, bp, si, di são escritos sequencialmente na pilha. Observe que o conteúdo original de sp é escrito, ou seja, o conteúdo que estava antes da emissão do comando pusha (Fig. 26).

Arroz. 26. Como funciona o comando pusha

4. pushaw é quase sinônimo de comando pusha Qual é a diferença? O atributo bitness pode ser use16 ou use32. Vejamos como os comandos pusha e pushaw funcionam com cada um desses atributos:

1) use16 - o algoritmo pushaw é semelhante ao algoritmo pusha;

2) use32 - pushaw não muda (ou seja, é insensível à largura do segmento e sempre funciona com registradores de tamanho de palavra - ax, cx, dx, bx, sp, bp, si, di). O comando pusha é sensível à largura do segmento definido e quando um segmento de 32 bits é especificado, ele funciona com os registros de 32 bits correspondentes, ou seja, eax, esx, edx, ebx, esp, ebp, esi, edi.

5. pushad - executado de forma semelhante ao comando pusha, mas existem algumas particularidades.

Os três comandos a seguir executam o inverso dos comandos acima:

1) rora;

2) pipoca;

3) pop.

O grupo de instruções descrito abaixo permite que você salve o registrador de flag na pilha e escreva uma palavra ou palavra dupla na pilha. Observe que as instruções listadas abaixo são as únicas no conjunto de instruções do microprocessador que permitem (e exigem) acesso a todo o conteúdo do registrador de flag.

1. pushf - salva o registro de flags na pilha.

A operação deste comando depende do atributo de tamanho do segmento:

1) use 16 - o registrador de flags de 2 bytes de tamanho é escrito na pilha;

2) use32 - o registrador eflags de 4 bytes é escrito na pilha.

2. pushfw - salvando um registro de flags do tamanho de uma palavra na pilha. Sempre funciona como pushf com o atributo use16.

3. pushfd - salvando os flags ou flags eflags registrados na pilha dependendo do atributo de largura de bits do segmento (ou seja, o mesmo que pushf).

Da mesma forma, os três comandos a seguir executam o inverso das operações discutidas acima:

1) popf;

2) popftv;

3) popfd.

E em conclusão, notamos os principais tipos de operações quando o uso da pilha é quase inevitável:

1) chamada de sub-rotinas;

2) armazenamento temporário dos valores dos registradores;

3) definição de variáveis ​​locais.

2. Instruções aritméticas

O microprocessador pode executar operações inteiras e de ponto flutuante. Para fazer isso, sua arquitetura possui dois blocos separados:

1) um dispositivo para realizar operações inteiras;

2) um dispositivo para realizar operações de ponto flutuante.

Cada um desses dispositivos tem seu próprio sistema de comando. Em princípio, um dispositivo inteiro pode assumir muitas das funções de um dispositivo de ponto flutuante, mas isso será computacionalmente caro. Para a maioria dos problemas usando linguagem assembly, a aritmética inteira é suficiente.

Visão geral de um grupo de instruções e dados aritméticos

Um dispositivo de computação inteiro suporta um pouco mais de uma dúzia de instruções aritméticas. A Figura 27 mostra a classificação dos comandos neste grupo.

Arroz. 27. Classificação de comandos aritméticos

O grupo de instruções aritméticas inteiras trabalha com dois tipos de números:

1) números binários inteiros. Os números podem ou não ter um dígito assinado, ou seja, ser números assinados ou não assinados;

2) números decimais inteiros.

Considere os formatos de máquina nos quais esses tipos de dados são armazenados.

Números binários inteiros

Um inteiro binário de ponto fixo é um número codificado no sistema numérico binário.

A dimensão de um inteiro binário pode ser de 8, 16 ou 32 bits. O sinal de um número binário é determinado por como o bit mais significativo na representação do número é interpretado. Isso é 7,15 ou 31 bits para números da dimensão correspondente. Ao mesmo tempo, é interessante que entre os comandos aritméticos existam apenas dois comandos que realmente levam em conta este bit mais significativo como um sinal, estes são os comandos de multiplicação e divisão de inteiros imul e idiv. Em outros casos, a responsabilidade pelas ações com números assinados e, portanto, com um bit de sinal é do programador. O intervalo de valores de um número binário depende de seu tamanho e interpretação do bit mais significativo como o bit mais significativo do número ou como o bit de sinal do número (Tabela 9).

Tabela 9. Faixa de números binários Números decimais

Os números decimais são um tipo especial de representação da informação numérica, que se baseia no princípio de codificar cada dígito decimal de um número por um grupo de quatro bits. Neste caso, cada byte do número contém um ou dois dígitos decimais no chamado código decimal codificado em binário (BCD - Binary-Coded Decimal). O microprocessador armazena números BCD em dois formatos (Fig. 28):

1) formato embalado. Nesse formato, cada byte contém dois dígitos decimais. Um dígito decimal é um valor binário de 0 bits entre 9 e 4. Neste caso, o código do dígito mais alto do número ocupa os 4 bits mais altos. Portanto, o intervalo de representação de um número decimal compactado em 1 byte é de 00 a 99;

2) formato não empacotado. Nesse formato, cada byte contém um dígito decimal nos quatro bits menos significativos. Os 4 bits superiores são definidos como zero. Esta é a chamada zona. Portanto, o intervalo de representação de um número decimal descompactado em 1 byte é de 0 a 9.

Arroz. 28. Representação de números BCD

Como descrever números decimais binários em um programa? Para fazer isso, você pode usar apenas duas diretivas de descrição e inicialização de dados - db e dt. A possibilidade de usar apenas essas diretivas para descrever números BCD se deve ao fato de que o princípio de "byte baixo em endereço baixo" também é aplicável a esses números, o que é muito conveniente para seu processamento. E, em geral, ao usar um tipo de dados como números BCD, a ordem em que esses números são descritos no programa e o algoritmo para processá-los é uma questão de gosto e preferências pessoais do programador. Isso ficará claro depois de analisarmos os fundamentos do trabalho com números BCD abaixo.

Operações aritméticas em inteiros binários

Adição de números binários não assinados

O microprocessador realiza a adição de operandos de acordo com as regras de adição de números binários. Não há problemas desde que o valor do resultado não exceda as dimensões do campo do operando. Por exemplo, ao adicionar operandos de tamanho byte, o resultado não deve exceder o número 255. Se isso acontecer, o resultado está incorreto. Vamos considerar por que isso acontece.

Por exemplo, vamos fazer a adição: 254 + 5 = 259 em binário. 11111110 + 0000101 = 1 00000011. O resultado foi além de 8 bits e seu valor correto cabe em 9 bits, e o valor 8 permaneceu no campo de 3 bits do operando, o que, obviamente, não é verdade. No microprocessador, esse resultado da adição é previsto e são fornecidos meios especiais para corrigir tais situações e processá-las. Assim, para corrigir a situação de ir além da grade de bits do resultado, como neste caso, o sinalizador de transporte cf é pretendido. Ele está localizado no bit 0 do registrador de flag EFLAGS/FLAGS. É a configuração deste sinalizador que corrige o fato da transferência de um da ordem superior do operando. Naturalmente, o programador deve levar em conta a possibilidade de tal resultado da operação de adição e fornecer meios para correção. Isso envolve incluir seções de código após a operação de adição na qual o sinalizador cf é analisado. Este sinalizador pode ser analisado de várias maneiras.

O mais fácil e acessível é usar o comando de ramificação condicional jcc. Esta instrução tem como operando o nome da etiqueta no segmento de código atual. A transição para este rótulo é realizada se, como resultado da operação do comando anterior, o sinalizador cf for definido como 1. Existem três comandos de adição binários no sistema de comandos do microprocessador:

1) operando inc - operação de incremento, ou seja, aumentar o valor do operando em 1;

2) add operando_1, operando_2 - instrução de adição com o princípio de funcionamento: operando_1 = operando_1 + operando_2;

3) adc operando_1, operando_2 - instrução de adição levando em consideração o carry flag cf. Princípio de operação do comando: operando_1 = operando_1 + operando_2 + valor_sG.

Preste atenção ao último comando - este é o comando de adição, que leva em consideração a transferência de um de ordem superior. Já consideramos o mecanismo para o surgimento de tal unidade. Assim, a instrução adc é uma ferramenta do microprocessador para adicionar números binários longos, cujas dimensões excedem os comprimentos dos campos padrão suportados pelo microprocessador.

Adição Binária Assinada

Na verdade, o microprocessador "não está ciente" da diferença entre números assinados e não assinados. Em vez disso, ele tem os meios de corrigir a ocorrência de situações características que se desenvolvem no processo de cálculos. Cobrimos alguns deles ao discutir a adição não assinada:

1) o cf carry flag, definindo-o como 1, indica que os operandos estavam fora de alcance;

2) o comando adc, que leva em consideração a possibilidade de tal saída (transportar do bit menos significativo).

Outro meio é registrar o estado do bit de alta ordem (sinal) do operando, o que é feito usando o sinalizador de estouro no registrador EFLAGS (bit 11).

Claro, você se lembra de como os números são representados em um computador: positivo - em binário, negativo - em complemento de dois. Considere várias opções para adicionar números. Os exemplos pretendem mostrar o comportamento dos dois bits mais significativos dos operandos e a correção do resultado da operação de adição.

Exemplo

30566 = 0111011101100110

+

00687 = 00000010

=

31253 = 01111010

Monitoramos as transferências do 14º e 15º dígitos e a exatidão do resultado: não há transferências, o resultado está correto.

Exemplo

30566 = 0111011101100110

+

30566 = 0111011101100110

=

1132 = 11101110

Houve uma transferência da 14ª categoria; não há transferência da 15ª categoria. O resultado está errado, porque há um estouro - o valor do número acabou sendo maior do que um número com sinal de 16 bits (+32 767) pode ter.

Exemplo

-30566 = 10001000 10011010

+

-04875 = 11101100 11110101

=

-35441 = 01110101 10001111

Houve transferência do 15º dígito, não há transferência do 14º dígito. O resultado está incorreto, porque em vez de um número negativo, acabou sendo positivo (o bit mais significativo é 0).

Exemplo

-4875 = 11101100 11110101

+

-4875 = 11101100 11110101

=

09750 = 11011001

Há transferências do 14º e 15º bits. O resultado está correto.

Assim, examinamos todos os casos e descobrimos que a situação de estouro (definindo o sinalizador OF para 1) ocorre durante a transferência:

1) a partir do 14º dígito (para números positivos com sinal);

2) a partir do 15º dígito (para números negativos).

Por outro lado, não ocorre overflow (ou seja, o sinalizador OF é redefinido para 0) se houver um transporte de ambos os bits ou se não houver transporte em ambos os bits.

Portanto, o estouro é registrado com o sinalizador de estouro de. Além do sinalizador de, ao transferir do bit de ordem superior, o sinalizador de transferência CF é definido como 1. Como o microprocessador não sabe da existência de números com e sem sinal, o programador é o único responsável pelas ações corretas com os números resultantes. Você pode analisar os sinalizadores CF e OF com as instruções de salto condicional JC\JNC e JO\JNO, respectivamente.

Quanto aos comandos para adicionar números com sinal, eles são os mesmos que para números sem sinal.

Subtração de números binários sem sinal

Assim como na análise da operação de adição, discutiremos a essência dos processos que ocorrem ao realizar a operação de subtração. Se o minuendo for maior que o subtraendo, não há problema - a diferença é positiva, o resultado está correto. Se o minuendo for menor que o subtraído, há um problema: o resultado é menor que 0, e este já é um número com sinal. Nesse caso, o resultado deve ser encapsulado. O que isto significa? Com a subtração usual (em uma coluna), eles fazem um empréstimo de 1 da ordem mais alta. O microprocessador faz o mesmo, ou seja, pega 1 do dígito seguinte ao mais alto na grade de bits do operando. Vamos explicar com um exemplo.

Exemplo

05 = 00000000

-10 = 00000000 00001010

Para fazer a subtração, vamos fazer

empréstimo imaginário sênior:

100000000 00000101

-

00000000 00001010

=

11111111 11111011

Assim, em essência, a ação

(65 + 536) - 5 = 10

0 aqui é, por assim dizer, equivalente ao número 65536. O resultado, claro, está incorreto, mas o microprocessador considera que está tudo bem, embora corrija o fato de pedir uma unidade emprestada definindo o sinalizador de transporte cf. Mas observe novamente com atenção o resultado da operação de subtração. É -5 em complemento de dois! Vamos fazer um experimento: represente a diferença como uma soma de 5 + (-10).

Exemplo

5 = 00000000

+

(-10)= 11111111 11110110

=

11111111 11111011

ou seja, obtivemos o mesmo resultado do exemplo anterior.

Assim, após o comando para subtrair números sem sinal, é necessário analisar o estado do sinalizador CE. Se estiver definido como 1, isso indica que houve um empréstimo da ordem superior e o resultado foi obtido em um código adicional .

Assim como as instruções de adição, o grupo de instruções de subtração consiste no menor conjunto possível. Esses comandos realizam a subtração de acordo com os algoritmos que estamos considerando agora, e as exceções devem ser levadas em consideração pelo próprio programador. Os comandos de subtração incluem:

1) operando dec - operação de decremento, ou seja, diminui o valor do operando em 1;

2) sub operando_1, operando_2 - comando de subtração; seu princípio de funcionamento: operando_1 = operando_1 - operando_2;

3) sbb operando_1, operando_2 - comando de subtração levando em consideração o empréstimo (ci flag): operando_1 = operando_1 - operando_2 - valor_sG.

Como você pode ver, entre os comandos de subtração existe um comando sbb que leva em consideração a flag de carry cf. Este comando é semelhante ao adc, mas agora o sinalizador cf atua como um indicador de empréstimo de 1 do dígito mais significativo ao subtrair números.

Subtração binária assinada

Aqui tudo é um pouco mais complicado. O microprocessador não precisa ter dois dispositivos - adição e subtração. Basta ter apenas um - o dispositivo de adição. Mas para a subtração por meio da adição de números com um sinal em um código adicional, é necessário representar ambos os operandos - tanto o reduzido quanto o subtraído. O resultado também deve ser tratado como um valor de complemento de dois. Mas aqui surgem as dificuldades. Em primeiro lugar, eles estão relacionados ao fato de que o bit mais significativo do operando é considerado um bit de sinal. Considere o exemplo da subtração de 45 - (-127).

Exemplo

Subtração de números assinados 1

45 = 0010

-

-127 = 1000 0001

=

-44 = 1010 1100

A julgar pelo bit de sinal, o resultado acabou sendo negativo, o que, por sua vez, indica que o número deve ser considerado um complemento igual a -44. O resultado correto deve ser 172. Aqui, como no caso da adição com sinal, encontramos um estouro de mantissa, quando o bit significativo do número mudou o bit de sinal do operando. Você pode acompanhar essa situação pelo conteúdo do sinalizador de estouro de. Defini-lo como 1 indica que o resultado está fora do intervalo de números com sinal (ou seja, o bit mais significativo foi alterado) para um operando desse tamanho e o programador deve tomar medidas para corrigir o resultado.

Exemplo

Subtração de números assinados 2

-45-45 = -45 + (-45) = -90.

-45 = 11010011

+

-45 = 11010011

=

-90 = 1010 0110

Tudo está bem aqui, o sinalizador de estouro de é redefinido para 0 e 1 no bit de sinal indica que o valor do resultado é um número de complemento de dois.

Subtração e adição de operandos grandes

Se você notar, as instruções de adição e subtração funcionam com operandos de dimensão fixa: 8, 16, 32 bits. Mas e se você precisar adicionar números de uma dimensão maior, por exemplo 48 bits, usando operandos de 16 bits? Por exemplo, vamos adicionar dois números de 48 bits:

Arroz. 29. Adicionando operandos grandes

A Figura 29 mostra a tecnologia para adicionar números longos passo a passo. Pode-se ver que o processo de adição de números multi-byte ocorre da mesma forma que ao adicionar dois números "em uma coluna" - com a implementação, se necessário, de transferência de 1 para o bit mais alto. Se conseguirmos programar esse processo, expandiremos significativamente o intervalo de números binários nos quais podemos realizar operações de adição e subtração.

O princípio de subtração de números com uma faixa de representação que excede as grades de bits padrão dos operandos é o mesmo da adição, ou seja, o sinalizador de transporte cf é usado. Você só precisa imaginar o processo de subtração em uma coluna e combinar corretamente as instruções do microprocessador com a instrução sbb.

Para concluir nossa discussão sobre as instruções de adição e subtração, além do cf e dos sinalizadores, existem alguns outros sinalizadores no registrador eflags que podem ser usados ​​com instruções aritméticas binárias. Estas são as seguintes bandeiras:

1) zf - sinalizador zero, que é definido como 1 se o resultado da operação for 0 e como 1 se o resultado não for igual a 0;

2) sf - sinalizador de sinal, cujo valor após as operações aritméticas (e não apenas) coincide com o valor do bit mais significativo do resultado, ou seja, com o bit 7, 15 ou 31. Assim, este sinalizador pode ser usado para operações em números assinados.

Multiplicação de números sem sinal

O comando para multiplicar números sem sinal é

mul fator_1

Como você pode ver, o comando contém apenas um operando multiplicador. O segundo operando factor_2 é especificado implicitamente. Sua localização é fixa e depende do tamanho dos fatores. Como, em geral, o resultado de uma multiplicação é maior que qualquer um de seus fatores, seu tamanho e localização também devem ser determinados de forma única. As opções para os tamanhos dos fatores e a colocação do segundo operando e o resultado são mostradas na Tabela 10.

Tabela 10. Arranjo dos operandos e resultado na multiplicação

Pode-se observar na tabela que o produto é composto por duas partes e, dependendo do tamanho dos operandos, é colocado em dois lugares - no lugar do fator_2 (parte inferior) e no registrador adicional ah, dx, edx (parte superior papel). Como, então, saber dinamicamente (ou seja, durante a execução do programa) que o resultado é pequeno o suficiente para caber em um registrador, ou que ultrapassou a dimensão do registrador e a parte mais alta foi parar em outro registrador? Para fazer isso, usamos os sinalizadores cf e overflow já conhecidos por nós da discussão anterior:

1) se a parte inicial do resultado for zero, então após a operação do produto os flags cf = 0 e of = 0;

2) se esses sinalizadores forem diferentes de zero, isso significa que o resultado foi além da menor parte do produto e consiste em duas partes, que devem ser levadas em consideração em trabalhos posteriores.

Multiplicar números assinados

O comando para multiplicar números com um sinal é

[imul operando_1, operando_2, operando_3]

Este comando é executado da mesma forma que o comando mul. Uma característica distintiva do comando imul é apenas a formação do signo.

Se o resultado for pequeno e couber em um registrador (isto é, se cf = of = 0), então o conteúdo do outro registrador (a parte alta) é extensão de sinal - todos os seus bits são iguais ao bit alto (bit de sinal ) da parte baixa do resultado. Caso contrário (se cf = of = 1), o sinal do resultado é o bit de sinal da parte alta do resultado e o bit de sinal da parte baixa é o bit significativo do código de resultado binário.

Divisão de números não assinados

O comando para dividir números sem sinal é

divisor div

O divisor pode estar na memória ou em um registrador e ter 8, 16 ou 32 bits de tamanho. A localização do dividendo é fixa e, como na instrução de multiplicação, depende do tamanho dos operandos. O resultado do comando de divisão são os valores do quociente e do resto.

As opções de localização e tamanho dos operandos da operação de divisão são mostradas na Tabela 11.

Tabela 11. Arranjo dos operandos e resultado na divisão

Depois que a instrução de divisão é executada, o conteúdo dos sinalizadores fica indefinido, mas a interrupção número 0, chamada "dividir por zero", pode ocorrer. Este tipo de interrupção pertence às chamadas exceções. Este tipo de interrupção ocorre dentro do microprocessador devido a algumas anomalias durante o processo de computação. Interromper O, "dividir por zero", durante a execução do comando div pode ocorrer por um dos seguintes motivos:

1) o divisor é zero;

2) o quociente não está incluído na grade de bits alocada para ele, o que pode acontecer nos seguintes casos:

a) ao dividir um dividendo com valor de palavra por um divisor com valor de bytes, e o valor do dividendo for mais de 256 vezes maior que o valor do divisor;

b) ao dividir um dividendo com valor de palavra dupla por um divisor com valor de palavra, e o valor do dividendo for mais de 65 vezes maior que o valor do divisor;

c) ao dividir o dividendo com valor de palavra quádruplo por um divisor com valor de palavra duplo, e o valor do dividendo for mais de 4 vezes o valor do divisor.

Divisão com um sinal

O comando para dividir números com um sinal é

divisor idiv

Para este comando, todas as disposições consideradas sobre comandos e números assinados são válidas. Apenas notamos as características da ocorrência da exceção 0, "divisão por zero", no caso de números com sinal. Ocorre ao executar o comando idiv por um dos seguintes motivos:

1) o divisor é zero;

2) o quociente não está incluído na grade de bits alocada para ele.

Este último, por sua vez, pode acontecer:

1) ao dividir um dividendo com valor de palavra com sinal por um divisor com valor de byte com sinal, e o valor do dividendo for mais de 128 vezes o valor do divisor (assim, o quociente não deve estar fora do intervalo de -128 para + 127);

2) ao dividir o dividendo por um valor de palavra dupla sinalizada pelo divisor por um valor de palavra sinalizada, e o valor do dividendo for superior a 32 vezes o valor do divisor (portanto, o quociente não deve estar fora do intervalo de - 768 a +32);

3) ao dividir o dividendo por um valor quadword com sinal por um divisor de palavra dupla com sinal, e o valor do dividendo for superior a 2 vezes o valor do divisor (portanto, o quociente não deve estar fora do intervalo de -147 a + 483 648 2 147).

Instruções Auxiliares para Operações Inteiras

Existem várias instruções no conjunto de instruções do microprocessador que podem facilitar a programação de algoritmos que executam cálculos aritméticos. Vários problemas podem surgir neles, para cuja resolução os desenvolvedores de microprocessadores forneceram vários comandos.

Comandos de conversão de tipo

E se os tamanhos dos operandos envolvidos nas operações aritméticas forem diferentes? Por exemplo, suponha que em uma operação de adição, um operando seja uma palavra e o outro seja uma palavra dupla. Foi dito acima que operandos do mesmo formato devem participar da operação de adição. Se os números não forem assinados, a saída será fácil de encontrar. Neste caso, com base no operando original, um novo (formato de palavra dupla) pode ser formado, cujos bits altos podem ser simplesmente preenchidos com zeros. A situação é mais complicada para números assinados: como levar em conta o sinal do operando dinamicamente, durante a execução do programa? Para resolver tais problemas, o conjunto de instruções do microprocessador possui as chamadas instruções de conversão de tipo. Essas instruções expandem bytes em palavras, palavras em palavras duplas e palavras duplas em palavras quádruplas (valores de 64 bits). As instruções de conversão de tipo são especialmente úteis na conversão de inteiros com sinal, pois preenchem automaticamente os bits de ordem superior do operando recém-construído com os valores do bit de sinal do objeto antigo. Essa operação resulta em valores inteiros de mesmo sinal e mesma magnitude do original, mas em formato mais longo. Essa transformação é chamada de operação de propagação de sinal.

Existem dois tipos de comandos de conversão de tipo.

1. Instruções sem operandos. Esses comandos funcionam com registradores fixos:

1) cbw (Convert Byte to Word) - um comando para converter um byte (no registrador al) em uma palavra (no registrador ah) espalhando o valor do bit al alto para todos os bits do registrador ah;

2) cwd (Convert Word to Double) - um comando para converter uma palavra (no registrador ax) em uma palavra dupla (nos registradores dx:ax) espalhando o valor do bit alto ax para todos os bits do registrador dx;

3) cwde (Convert Word to Double) - um comando para converter uma palavra (no registrador ax) em uma palavra dupla (no registrador eax) espalhando o valor do bit alto ax para todos os bits da metade superior do registrador eax ;

4) cdq (Convert Double Word to Quarter Word) - um comando para converter uma palavra dupla (no registrador eax) em uma palavra quádrupla (nos registradores edx: eax) espalhando o valor do bit mais significativo de eax para todos bits do registrador edx.

2. Comandos movsx e movzx relacionados a comandos de processamento de strings. Esses comandos têm uma propriedade útil no contexto do nosso problema:

1) movsx operando_1, operando_2 - envia com propagação de sinal. Estende um valor de 8 ou 16 bits do operando_2, que pode ser um registrador ou um operando de memória, para um valor de 16 ou 32 bits em um dos registradores, usando o valor do bit de sinal para preencher as posições mais altas do operando_1. Esta instrução é útil para preparar operandos assinados para operações aritméticas;

2) movzx operando_1, operando_2 - envia com extensão zero. Estende o valor de 8 bits ou 16 bits do operando_2 para 16 bits ou 32 bits, limpando (preenchendo) as posições altas do operando_2 com zeros. Esta instrução é útil para preparar operandos sem sinal para aritmética.

Outros comandos úteis

1. xadd destino, origem - troca e adição.

O comando permite que você execute duas ações em sequência:

1) valores de destino e origem da troca;

2) coloque o operando destino no lugar da soma: destino = destino + origem.

2. operando neg - negação com complemento de dois.

A instrução inverte o valor do operando. Fisicamente, o comando executa uma ação:

operando = 0 - operando, ou seja, subtrai o operando de zero.

O comando operando neg pode ser usado:

1) mudar o sinal;

2) para realizar a subtração de uma constante.

Operações aritméticas em números binários decimais

Nesta seção, veremos as especificidades de cada uma das quatro operações aritméticas básicas para números BCD compactados e descompactados.

A questão pode surgir com razão: por que precisamos de números BCD? A resposta pode ser: os números BCD são necessários em aplicativos de negócios, ou seja, onde os números precisam ser grandes e precisos. Como já vimos no exemplo dos números binários, as operações com esses números são bastante problemáticas para a linguagem assembly. As desvantagens de usar números binários incluem o seguinte:

1) Os valores em formato word e double word possuem um intervalo limitado. Se o programa for projetado para funcionar no campo das finanças, limitar o valor em rublos a 65 (para uma palavra) ou até 536 (para uma palavra dupla) restringirá significativamente o escopo de sua aplicação;

2) a presença de erros de arredondamento. Já imaginou um programa rodando em algum lugar de um banco que não leva em conta o valor do saldo ao operar com inteiros binários e opera com bilhões? Eu não gostaria de ser o autor de tal programa. O uso de números de ponto flutuante não salvará - o mesmo problema de arredondamento existe lá;

3) apresentação de uma grande quantidade de resultados em forma simbólica (código ASCII). Os programas de negócios não fazem apenas cálculos; uma das finalidades de seu uso é a pronta entrega de informações ao usuário. Para fazer isso, é claro, a informação deve ser apresentada de forma simbólica. Converter números de binário para ASCII requer algum esforço computacional. Um número de ponto flutuante é ainda mais difícil de traduzir em uma forma simbólica. Mas se você observar a representação hexadecimal de um dígito decimal descompactado e seu caractere correspondente na tabela ASCII, poderá ver que eles diferem em 30h. Assim, a conversão para a forma simbólica e vice-versa é muito mais fácil e rápida.

Você provavelmente já viu a importância de dominar pelo menos o básico das ações com números decimais. Em seguida, considere os recursos de realizar operações aritméticas básicas com números decimais. Notamos imediatamente o fato de que não existem comandos separados para adição, subtração, multiplicação e divisão de números BCD. Isso foi feito por razões bastante compreensíveis: a dimensão de tais números pode ser arbitrariamente grande. Os números BCD podem ser somados e subtraídos, tanto compactados quanto descompactados, mas apenas números BCD descompactados podem dividir e multiplicar. Por que isso é assim será visto a partir de uma discussão mais aprofundada.

Aritmética em números BCD descompactados

Adicionar números BCD descompactados

Vamos considerar dois casos de adição.

Exemplo

O resultado da adição não é superior a 9

6 = 0000

+

3 = 0000

=

9 = 0000

Não há transferência da tétrade júnior para a sénior. O resultado está correto.

Exemplo

O resultado da adição é maior que 9:

06 = 0000

+

07 = 0000

=

13 = 0000

Não recebemos mais um número BCD. O resultado está errado. O resultado correto no formato BCD descompactado deve ser 0000 0001 0000 0011 em binário (ou 13 em decimal).

Depois de analisar este problema ao adicionar números BCD (e problemas semelhantes ao realizar outras operações aritméticas) e possíveis maneiras de resolvê-lo, os desenvolvedores do sistema de comando do microprocessador decidiram não introduzir comandos especiais para trabalhar com números BCD, mas introduzir vários comandos corretivos .

O objetivo dessas instruções é corrigir o resultado da operação de instruções aritméticas comuns para os casos em que os operandos nelas são números BCD.

No caso da subtração no exemplo 10, percebe-se que o resultado obtido precisa ser corrigido. Para corrigir a operação de adição de dois números BCD descompactados de um dígito no sistema de comando do microprocessador, existe um comando especial - aaa (ASCII Adjust for Addition) - correção do resultado da adição para representação em forma simbólica.

Esta instrução não possui operandos. Ele funciona implicitamente apenas com o registro al e analisa o valor de sua tétrade inferior:

1) se este valor for menor que 9, então o flag cf é resetado para XNUMX e a transição para a próxima instrução é realizada;

2) se este valor for maior que 9, as seguintes ações são executadas:

a) 6 é adicionado ao conteúdo do tetrad al inferior (mas não ao conteúdo de todo o registro!) Assim, o valor do resultado decimal é corrigido na direção correta;

b) o sinalizador cf é definido como 1, fixando assim a transferência para o bit mais significativo para que possa ser levado em consideração nas ações subsequentes.

Assim, no exemplo 10, assumindo que o valor da soma 0000 1101 está em al, após a instrução aaa, o registrador terá 1101 + 0110 = 0011, ou seja, binário 0000 0011 ou decimal 3, e o sinalizador cf será definido como 1, ou seja, a transferência foi armazenada no microprocessador. Em seguida, o programador precisará usar a instrução de adição adc, que levará em conta o carry do bit anterior.

Subtração de números BCD descompactados

A situação aqui é bastante semelhante à adição. Vamos considerar os mesmos casos.

Exemplo

O resultado da subtração não é maior que 9:

6 = 0000

-

3 = 0000

=

3 = 0000

Como você pode ver, não há empréstimo do caderno sênior. O resultado está correto e não requer correção.

Exemplo

O resultado da subtração é maior que 9:

6 = 0000

-

7 = 0000

=

-1 = 1111 1111

A subtração é realizada de acordo com as regras da aritmética binária. Portanto, o resultado não é um número BCD.

O resultado correto no formato BCD descompactado deve ser 9 (0000 1001 em binário). Neste caso, assume-se um empréstimo do dígito mais significativo, como em um comando de subtração normal, ou seja, no caso de números BCD, a subtração de 16 - 7 deve ser realizada. caso de adição, o resultado da subtração deve ser corrigido. Para isso, existe um comando especial - aas (ASCII Adjust for Substraction) - correção do resultado da subtração para representação na forma simbólica.

A instrução aas também não possui operandos e opera no registrador al, analisando sua tétrade de menor ordem da seguinte forma:

1) se seu valor for menor que 9, então o sinalizador cf é redefinido para 0 e o controle é transferido para o próximo comando;

2) se o valor tetrad em al for maior que 9, o comando aas executa as seguintes ações:

a) subtrai 6 do conteúdo da tétrade inferior do registro al (nota - não do conteúdo de todo o registro);

b) redefine a tétrade superior do registrador al;

c) define o sinalizador cf como 1, fixando assim o empréstimo imaginário de alta ordem.

É claro que o comando aas é usado em conjunto com os comandos básicos de subtração sub e sbb. Neste caso, faz sentido usar o comando sub apenas uma vez, ao subtrair os dígitos mais baixos dos operandos, então deve-se usar o comando sbb, que levará em conta um possível empréstimo da ordem mais alta.

Multiplicação de números BCD descompactados

Usando o exemplo de adição e subtração de números descompactados, ficou claro que não existem algoritmos padrão para realizar essas operações em números BCD, e o próprio programador deve, com base nos requisitos de seu programa, implementar essas operações.

A implementação das duas operações restantes - multiplicação e divisão - é ainda mais complicada. No conjunto de instruções do microprocessador, existem apenas meios para a produção de multiplicação e divisão de números BCD descompactados de um dígito.

Para multiplicar números de dimensão arbitrária, você mesmo precisa implementar o processo de multiplicação, com base em algum algoritmo de multiplicação, por exemplo, "em uma coluna".

Para multiplicar dois números BCD de um dígito, você deve:

1) colocar um dos fatores no registro AL (conforme exigido pela instrução mul);

2) colocar o segundo operando em um registrador ou memória, alocando um byte;

3) multiplique os fatores com o comando mul (o resultado, como esperado, ficará em ah);

4) o resultado, claro, estará em código binário, então precisa ser corrigido.

Para corrigir o resultado após a multiplicação, é utilizado um comando especial - aam (ASCII Adjust for Multiplication) - correção do resultado da multiplicação para representação em forma simbólica.

Não possui operandos e opera no registrador AX da seguinte forma:

1) divide al por 10;

2) o resultado da divisão é escrito da seguinte forma: quociente em al, resto em ah. Como resultado, após a execução da instrução aam, os registradores AL e ah contêm os dígitos BCD corretos do produto de dois dígitos.

Antes de encerrarmos nossa discussão sobre o comando aam, precisamos observar mais um uso para ele. Este comando pode ser usado para converter um número binário no registro AL em um número BCD descompactado, que será colocado no registro ah: o dígito mais significativo do resultado está em ah, o dígito menos significativo está em al. É claro que o número binário deve estar no intervalo 0...99.

Divisão de números BCD descompactados

O processo de realizar a operação de divisão de dois números BCD descompactados é um pouco diferente das outras operações consideradas anteriormente com eles. Ações de correção também são necessárias aqui, mas devem ser realizadas antes da operação principal que divide diretamente um número BCD por outro número BCD. Primeiro, no registro ah, você precisa obter dois dígitos BCD descompactados do dividendo. Isso deixa o programador confortável para ele de certa forma. Em seguida, você precisa emitir o comando aad - aad (ASCII Adjust for Division) - correção de divisão para representação simbólica.

A instrução não possui operandos e converte o número BCD descompactado de dois dígitos no registrador ax em um número binário. Este número binário desempenhará posteriormente o papel do dividendo na operação de divisão. Além da conversão, o comando aad coloca o número binário resultante no registrador AL. O dividendo será naturalmente um número binário no intervalo 0...99.

O algoritmo pelo qual o comando aad realiza essa conversão é o seguinte:

1) multiplique o dígito mais alto do número BCD original em ah (o conteúdo de AH) por 10;

2) execute a adição AH + AL, cujo resultado (número binário) é inserido em AL;

3) redefinir o conteúdo de AH.

Em seguida, o programador precisa emitir um comando de divisão div normal para realizar a divisão do conteúdo de ax por um único dígito BCD localizado em um registrador de bytes ou em um local de memória de bytes.

Semelhante ao aash, o comando aad também pode ser usado para converter números BCD descompactados do intervalo 0...99 para seu equivalente binário.

Para dividir números de maior capacidade, assim como no caso de multiplicação, você precisa implementar seu próprio algoritmo, por exemplo, "em uma coluna", ou encontrar uma maneira mais otimizada.

Aritmética em números BCD compactados

Conforme observado acima, os números BCD compactados só podem ser adicionados e subtraídos. Para executar outras ações neles, eles devem ser convertidos adicionalmente em um formato descompactado ou em uma representação binária. Devido ao fato de que os números BCD compactados não são de grande interesse, vamos considerá-los brevemente.

Adicionando números BCD compactados

Primeiro, vamos chegar ao cerne do problema e tentar adicionar dois números BCD compactados de dois dígitos. Exemplo de adição de números BCD compactados:

67 = 01100111

+

75 = 01110101

=

142 = 1101 1100 = 220

Como você pode ver, em binário o resultado é 1101 1100 (ou 220 em decimal), o que é incorreto. Isso ocorre porque o microprocessador não tem conhecimento da existência de números BCD e os soma de acordo com as regras de adição de números binários. Na verdade, o resultado em BCD deve ser 0001 0100 0010 (ou 142 em decimal).

Pode-se ver que, assim como para números BCD não empacotados, para números BCD empacotados há a necessidade de corrigir de alguma forma os resultados das operações aritméticas.

O microprocessador disponibiliza para este comando daa - daa (Ajuste Decimal para Adição) - correção do resultado da adição para apresentação na forma decimal.

O comando daa converte o conteúdo do registrador al em dois dígitos decimais compactados de acordo com o algoritmo dado na descrição do comando daa. A unidade resultante (se o resultado da adição for maior que 99) é armazenada no sinalizador cf, levando assim em conta a transferência para o bit mais significativo.

Subtração de números BCD compactados

Semelhante à adição, o microprocessador trata os números BCD compactados como binários e subtrai os números BCD como binários de acordo.

Exemplo

Subtração de números BCD compactados.

Vamos subtrair 67-75. Como o microprocessador realiza a subtração na forma de adição, seguiremos o seguinte:

67 = 01100111

+

-75 = 10110101

=

-8 = 0001 1100 = 28

Como você pode ver, o resultado é 28 em decimal, o que é um absurdo. Em BCD, o resultado deve ser 0000 1000 (ou 8 em decimal).

Ao programar a subtração de números BCD compactados, o programador, bem como ao subtrair números BCD descompactados, deve controlar o próprio sinal. Isso é feito usando o sinalizador CF, que corrige o empréstimo de alta ordem.

A própria subtração de números BCD é realizada por um simples comando de subtração sub ou sbb. A correção do resultado é realizada pelo comando das - das (Ajuste Decimal para Subtração) - correção do resultado da subtração para representação na forma decimal.

O comando das converte o conteúdo do registrador AL para dois dígitos decimais compactados de acordo com o algoritmo fornecido na descrição do comando das.

PALESTRA Nº 19. Comandos de transferência de controle

1. Comandos lógicos

Juntamente com os meios de cálculos aritméticos, o sistema de comando do microprocessador também possui meios de conversão lógica de dados. Por meios lógicos tais transformações de dados, que são baseadas nas regras da lógica formal.

A lógica formal opera no nível de declarações verdadeiras e falsas. Para um microprocessador, isso geralmente significa 1 e 0, respectivamente. Para um computador, a linguagem de zeros e uns é nativa, mas a unidade mínima de dados com a qual as instruções de máquina funcionam é um byte. No entanto, no nível do sistema, muitas vezes é necessário poder operar no nível mais baixo possível, o nível de bits.

Arroz. 29. Meios de processamento lógico de dados

Os meios de transformação lógica de dados incluem comandos lógicos e operações lógicas. O operando de uma instrução assembler geralmente pode ser uma expressão, que por sua vez é uma combinação de operadores e operandos. Entre esses operadores podem existir operadores que implementam operações lógicas em objetos de expressão.

Antes de considerar essas ferramentas em detalhes, vamos considerar quais são os próprios dados lógicos e quais operações são executadas neles.

Dados booleanos

A base teórica para o processamento lógico de dados é a lógica formal. Existem vários sistemas de lógica. Um dos mais famosos é o cálculo proposicional. Uma proposição é qualquer afirmação que pode ser considerada verdadeira ou falsa.

O cálculo proposicional é um conjunto de regras usadas para determinar a verdade ou falsidade de alguma combinação de proposições.

O cálculo proposicional combina-se muito harmoniosamente com os princípios do computador e os métodos básicos de sua programação. Todos os componentes de hardware de um computador são construídos em chips lógicos. O sistema de representação de informações em um computador no nível mais baixo é baseado no conceito de bit. Um bit, tendo apenas dois estados (0 (falso) e 1 (verdadeiro)), se encaixa naturalmente no cálculo proposicional.

De acordo com a teoria, as seguintes operações lógicas podem ser executadas em instruções (em bits).

1. Negação (NOT lógico) - uma operação lógica em um operando, cujo resultado é o recíproco do valor do operando original.

Esta operação é caracterizada exclusivamente pela seguinte tabela verdade (Tabela 12).

Tabela 12. Tabela verdade para negação lógica

2. Adição lógica (OR inclusivo lógico) - uma operação lógica em dois operandos, cujo resultado é "verdadeiro" (1) se um ou ambos os operandos forem verdadeiros (1), e "falso" (0) se ambos os operandos forem falso (0).

Esta operação é descrita usando a seguinte tabela verdade (Tabela 13).

Tabela 13. Tabela verdade para OR lógico inclusivo

3. Multiplicação lógica (AND lógico) - uma operação lógica em dois operandos, cujo resultado é verdadeiro (1) somente se ambos os operandos forem verdadeiros (1). Em todos os outros casos, o valor da operação é "false" (0).

Esta operação é descrita usando a seguinte tabela verdade (Tabela 14).

Tabela 14. Lógica E tabela verdade

4. Adição exclusiva lógica (OR exclusivo lógico) - uma operação lógica em dois operandos, cujo resultado é "verdadeiro" (1), se apenas um dos dois operandos for verdadeiro (1), e falso (0), se ambos os operandos são falsos (0) ou verdadeiros (1). Esta operação é descrita usando a seguinte tabela verdade (Tabela 15).

Tabela 15. Tabela verdade para XOR lógico

O conjunto de instruções do microprocessador contém cinco instruções que suportam essas operações. Essas instruções realizam operações lógicas nos bits dos operandos. As dimensões dos operandos, é claro, devem ser as mesmas. Por exemplo, se a dimensão dos operandos for igual à palavra (16 bits), então a operação lógica é executada primeiro nos bits zero dos operandos, e seu resultado é escrito no lugar do bit 0 do resultado. Em seguida, o comando repete essas ações sequencialmente em todos os bits do primeiro ao décimo quinto.

Comandos lógicos

O sistema de comando do microprocessador possui o seguinte conjunto de comandos que suporta o trabalho com dados lógicos:

1) e operando_1, operando_2 - operação de multiplicação lógica. O comando realiza uma operação lógica AND (conjunção) bit a bit nos bits dos operandos operando_1 e operando_2. O resultado é escrito no lugar do operando_1;

2) og operando_1, operando_2 - operação lógica de adição. O comando executa uma operação OR lógica bit a bit (disjunção) nos bits dos operandos operando_1 e operando_2. O resultado é escrito no lugar de operando_1;

3) xor operando_1, operando_2 - operação de adição lógica exclusiva. O comando executa uma operação XOR lógica bit a bit nos bits dos operandos operando_1 e operando_2. O resultado é escrito no lugar do operando;

4) test operando_1, operando_2 - operação "teste" (usando o método de multiplicação lógica). O comando executa uma operação AND lógica bit a bit nos bits dos operandos operando_1 e operando_2. O estado dos operandos permanece o mesmo, apenas os flags zf, sf e pf são alterados, o que possibilita analisar o estado de bits individuais do operando sem alterar seu estado;

5) não operando - operação de negação lógica. O comando realiza uma inversão bit a bit (substituindo o valor pelo oposto) de cada bit do operando. O resultado é escrito no lugar do operando.

Para entender o papel dos comandos lógicos no conjunto de instruções do microprocessador, é muito importante entender as áreas de sua aplicação e os métodos típicos de seu uso na programação.

Com a ajuda de comandos lógicos, é possível selecionar bits individuais no operando com a finalidade de configurá-los, restaurá-los, invertê-los ou simplesmente verificar um determinado valor.

Para organizar esse trabalho com bits, o operando_2 geralmente desempenha o papel de uma máscara. Com a ajuda dos bits desta máscara definidos no bit 1, são determinados os bits operando_1 necessários para uma determinada operação. Vamos mostrar quais comandos lógicos podem ser usados ​​para essa finalidade:

1) para definir certos dígitos (bits) para 1, o comando og operando_1, operando_2 é usado.

Nesta instrução, o operando_2, que funciona como máscara, deve conter 1 bit no lugar daqueles bits que devem ser setados em 1 no operando_XNUMX;

2) para redefinir determinados dígitos (bits) para 0, é usado o comando e operando_1, operando_2.

Nesta instrução, o operando_2, que funciona como máscara, deve conter zero bits no lugar daqueles bits que devem ser definidos como 0 no operando_1;

3) comando xor operando_1, operando_2 é aplicado:

a) descobrir quais bits no operando_1 e operando diferem;

b) inverter o estado dos bits especificados no operando_1.

Os bits de máscara que nos interessam (operando_2) ao executar o comando xor devem ser únicos, o restante deve ser zero;

O comando test operando_1, operando_2 (verificar operando_1) é utilizado para verificar o estado dos bits especificados.

Os bits verificados do operando_1 na máscara (operando_2) devem ser definidos como um. O algoritmo do comando test é semelhante ao algoritmo do comando and, mas não altera o valor do operando_1. O resultado do comando é definir o valor do sinalizador zero zf:

1) se zf = 0, então, como resultado da multiplicação lógica, obtém-se um resultado zero, ou seja, um bit unitário da máscara, que não corresponde ao bit unitário correspondente do operando;

2) se zf = 1, então, como resultado da multiplicação lógica, obtém-se um resultado diferente de zero, ou seja, pelo menos um bit unitário da máscara coincide com o bit unitário correspondente do operando_1.

Para reagir ao resultado do comando de teste, é aconselhável usar o comando jump jnz label (Jump if Not Zero) - jump se o sinalizador zero zf for diferente de zero, ou o comando reverse action - jz label (Jump if Zero ) - salta se o sinalizador zero zf = 0.

Os dois comandos a seguir procuram o primeiro bit do operando definido como 1. A busca pode ser realizada tanto do início quanto do final do operando:

1) bsf operando_1, operando_2 (Bit Scanning Forward) - varrendo bits para frente. A instrução procura (varre) os bits do operando_2 do menos significativo ao mais significativo (do bit 0 ao bit mais significativo) em busca do primeiro bit definido como 1. Se um for encontrado, o operando_1 é preenchido com o número de este bit como um valor inteiro. Se todos os bits do operando_2 forem 0, então o sinalizador zero zf será definido como 1, caso contrário, o sinalizador zf será redefinido para 0;

2) bsr operando_1, operando_2 (Bit Scanning Reset) - varre os bits na ordem inversa. A instrução pesquisa (varre) os bits do operando_2 do mais significativo ao menos significativo (do bit mais significativo ao bit 0) em busca do primeiro bit definido como 1. Se um for encontrado, o operando_1 é preenchido com o número de este bit como um valor inteiro. É importante que a posição do primeiro bit de unidade à esquerda ainda seja contada em relação ao bit 0. Se todos os bits do operando_2 forem 0, então o sinalizador zero zf será definido como 1, caso contrário, o sinalizador zf será redefinido para 0.

Nos modelos mais recentes de microprocessadores Intel, apareceram mais algumas instruções no grupo de instruções lógicas que permitem acessar um bit específico do operando. O operando pode estar na memória ou em um registrador geral. A posição do bit é dada pelo deslocamento do bit em relação ao bit menos significativo do operando. O valor de deslocamento pode ser especificado como um valor direto ou contido em um registro de uso geral. Você pode usar os resultados dos comandos bsr e bsf como o valor de deslocamento. Todas as instruções atribuem o valor do bit selecionado ao flag CE.

1) operando bt, bit_offset (teste de bits) - teste de bits. A instrução transfere o valor do bit para o sinalizador cf;

2) operando bts, offset_bit (Bit Test and Set) - verificando e configurando um bit. A instrução transfere o valor do bit para o sinalizador CF e, em seguida, define o bit a ser verificado em 1;

3) operando btr, bit_offset (Bit Test and Reset) - verificando e redefinindo um bit. A instrução transfere o valor do bit para o sinalizador CF e, em seguida, define esse bit como 0;

4) operando btc, offset_bit (Bit Test and Convert) - verificando e invertendo um bit. A instrução envolve o valor de um bit no sinalizador cf e então inverte o valor desse bit.

Comandos de Mudança

As instruções deste grupo também fornecem manipulação de bits individuais dos operandos, mas de uma maneira diferente das instruções lógicas discutidas acima.

Todas as instruções de deslocamento movem os bits no campo do operando para a esquerda ou para a direita, dependendo do opcode. Todas as instruções de deslocamento têm a mesma estrutura - copiar operando, shift_count.

O número de bits a serem deslocados - counter_shifts - está localizado no local do segundo operando e pode ser configurado de duas maneiras:

1) estaticamente, que envolve definir um valor fixo usando um operando direto;

2) dinamicamente, o que significa inserir o valor do contador de deslocamento no registrador cl antes de executar a instrução de deslocamento.

Com base na dimensão do registrador cl, fica claro que o valor do contador de deslocamento pode variar de 0 a 255. Mas, na verdade, isso não é inteiramente verdade. Para fins de otimização, o microprocessador aceita apenas o valor dos cinco bits menos significativos do contador, ou seja, o valor está na faixa de 0 a 31.

Todas as instruções de deslocamento definem o sinalizador de transporte cf.

À medida que os bits saem do operando, eles primeiro atingem o sinalizador de transporte, definindo-o igual ao valor do próximo bit fora do operando. Onde este bit vai em seguida depende do tipo de instrução de deslocamento e do algoritmo do programa.

Os comandos de deslocamento podem ser divididos em dois tipos de acordo com o princípio de operação:

1) comandos de deslocamento linear;

2) comandos de deslocamento cíclico.

Comandos de deslocamento linear

Comandos deste tipo incluem comandos que mudam de acordo com o seguinte algoritmo:

1) o próximo bit que é pressionado define o sinalizador CF;

2) o bit inserido no operando da outra extremidade tem o valor 0;

3) quando o próximo bit é deslocado, ele vai para o flag CF, enquanto o valor do bit deslocado anterior é perdido! Os comandos de deslocamento linear são divididos em dois subtipos:

1) comandos de deslocamento linear lógico;

2) instruções de deslocamento linear aritmético.

Os comandos de deslocamento linear lógico incluem o seguinte:

1) operando shl, counter_shifts (Shift Logical Left) - deslocamento lógico para a esquerda. O conteúdo do operando é deslocado para a esquerda pelo número de bits especificado por shift_count. À direita (na posição do bit menos significativo) são inseridos zeros;

2) operando shr, shift_count (Shift Logical Right) - deslocamento lógico para a direita. O conteúdo do operando é deslocado para a direita pelo número de bits especificado por shift_count. À esquerda (na posição do bit de sinal mais significativo), os zeros são inseridos.

A Figura 30 mostra como esses comandos funcionam.

Arroz. 30. Esquema de trabalho de comandos de deslocamento lógico linear

As instruções de deslocamento linear aritmético diferem das instruções de deslocamento lógico, pois operam no bit de sinal do operando de uma maneira especial.

1) operando sal, shift_counter (Shift Aritmética Esquerda) - deslocamento aritmético para a esquerda. O conteúdo do operando é deslocado para a esquerda pelo número de bits especificado por shift_count. À direita (na posição do bit menos significativo), os zeros são inseridos. A instrução sal não preserva o sinal, mas define o sinalizador com / no caso de uma mudança de sinal pelo próximo bit avançado. Caso contrário, o comando sal é exatamente igual ao comando shl;

2) operando sar, shift_count (Deslocamento Aritmético para a Direita) - deslocamento aritmético para a direita. O conteúdo do operando é deslocado para a direita pelo número de bits especificado por shift_count. Zeros são inseridos no operando à esquerda. O comando sar preserva o sinal, restaurando-o após cada troca de bit.

A Figura 31 mostra como funcionam as instruções de deslocamento aritmético linear.

Arroz. 31. Esquema de operação dos comandos de deslocamento aritmético linear

Comandos de rotação

As instruções de deslocamento cíclico incluem instruções que armazenam os valores dos bits deslocados. Existem dois tipos de instruções de deslocamento cíclico:

1) comandos simples de deslocamento cíclico;

2) comandos de deslocamento cíclico através do sinalizador de transporte cf.

Os comandos simples de deslocamento cíclico incluem:

1) rol operando, shift_counter (Rotate Left) - deslocamento cíclico para a esquerda. O conteúdo do operando é deslocado para a esquerda pelo número de bits especificado pelo operando shift_count. Os bits deslocados para a esquerda são escritos no mesmo operando da direita;

2) operando gog, counter_shifts (Rotate Right) - deslocamento cíclico para a direita. O conteúdo do operando é deslocado para a direita pelo número de bits especificado pelo operando shift_count. Os bits deslocados à direita são escritos no mesmo operando à esquerda.

Arroz. 32. Esquema de operação de comandos de um simples deslocamento cíclico

Como pode ser visto na Figura 32, as instruções de um simples deslocamento cíclico no curso de seu trabalho realizam uma ação útil, a saber: o bit deslocado ciclicamente não é apenas empurrado para o operando da outra extremidade, mas ao mesmo tempo seu value se torna o valor do sinalizador CE.

Os comandos de deslocamento cíclico através do sinalizador de transporte CF diferem dos comandos de deslocamento cíclico simples em que o bit deslocado não entra imediatamente no operando de sua outra extremidade, mas é escrito primeiro no sinalizador de transporte CE Somente a próxima execução deste comando de deslocamento ( desde que executado em loop) faz com que o bit previamente avançado seja colocado na outra extremidade do operando (Fig. 33).

O seguinte está relacionado aos comandos de deslocamento cíclico por meio do sinalizador de transporte:

1) operando rcl, shift_count (Rotate through Carry Left) - deslocamento cíclico para a esquerda através de carry.

O conteúdo do operando é deslocado para a esquerda pelo número de bits especificado pelo operando shift_count. Os bits deslocados, por sua vez, tornam-se o valor do sinalizador de transporte cf.

2) operando rsg, shift_count (Rotate through Carry Right) - deslocamento cíclico para a direita através de um carry.

O conteúdo do operando é deslocado para a direita pelo número de bits especificado pelo operando shift_count. Os bits deslocados, por sua vez, tornam-se o valor do sinalizador de transporte CF.

Arroz. 33. Girar Instruções via Carry Flag CF

A Figura 33 mostra que ao deslocar através do sinalizador de transporte, aparece um elemento intermediário, com a ajuda do qual, em particular, é possível substituir bits deslocados ciclicamente, em particular, o descasamento de sequências de bits.

Doravante, incompatibilidade de uma sequência de bits significa uma ação que permite de alguma forma localizar e extrair as seções necessárias dessa sequência e gravá-las em outro local.

Comandos de deslocamento adicionais

O sistema de comando dos modelos mais recentes de microprocessadores Intel, começando com o i80386, contém comandos shift adicionais que expandem os recursos discutidos anteriormente. Estes são os comandos de mudança de precisão dupla:

1) shld operando_1, operando_2, shift_counter - deslocamento à esquerda de precisão dupla. O comando shld realiza uma substituição deslocando os bits do operando_1 para a esquerda, preenchendo seus bits à direita com os valores dos bits deslocados do operando_2 conforme diagrama da Fig. 34. O número de bits a serem deslocados é determinado pelo valor shift_counter, que pode estar no intervalo de 0 a 31. Este valor pode ser especificado como um operando imediato ou contido no registrador cl. O valor do operando_2 não é alterado.

Arroz. 34. O esquema do comando shld

2) shrd operando_1, operando_2, shift_counter - deslocamento para a direita de precisão dupla. A instrução realiza a substituição deslocando os bits do operando_1 para a direita, preenchendo seus bits à esquerda com os valores dos bits deslocados do operando_2 conforme diagrama da Figura 35. A quantidade de bits deslocados é determinada por o valor do shift_counter, que pode estar no intervalo 0... 31. Este valor pode ser especificado pelo operando imediato ou contido no registro cl. O valor do operando_2 não é alterado.

Arroz. 35. O esquema do comando shrd

Como observamos, os comandos shld e shrd deslocam até 32 bits, mas devido às peculiaridades de especificação de operandos e do algoritmo de operação, esses comandos podem ser usados ​​para trabalhar com campos de até 64 bits.

2. Comandos de Transferência de Controle

Conhecemos alguns comandos a partir dos quais são formadas as seções lineares do programa. Cada um deles geralmente realiza alguma conversão ou transferência de dados, após o que o microprocessador transfere o controle para a próxima instrução. Mas muito poucos programas funcionam de forma tão consistente. Normalmente, há pontos em um programa em que uma decisão deve ser tomada sobre qual instrução será executada em seguida. Esta solução pode ser:

1) incondicional - neste ponto, é necessário transferir o controle não para o comando que vem a seguir, mas para outro, que está a alguma distância do comando atual;

2) condicional - a decisão sobre qual comando será executado em seguida é feita com base na análise de algumas condições ou dados.

Um programa é uma sequência de comandos e dados que ocupam uma certa quantidade de espaço na RAM. Esse espaço de memória pode ser contíguo ou consistir em vários fragmentos.

Qual instrução de programa deve ser executada em seguida, o microprocessador aprende com o conteúdo do cs: (e) par de registradores ip:

1) cs - registrador de segmento de código, que contém o endereço físico (base) do segmento de código atual;

2) eip/ip - registrador de ponteiro de instrução, que contém um valor que representa o deslocamento na memória da próxima instrução a ser executada em relação ao início do segmento de código atual.

Qual registrador específico será usado depende do modo de endereçamento definido use16 ou use32. Se usar 16 for especificado, então ip será usado, se usar32, então eip será usado.

Assim, as instruções de transferência de controle alteram o conteúdo dos registradores cs e eip/ip, como resultado do qual o microprocessador seleciona para execução não a próxima instrução do programa em ordem, mas a instrução em alguma outra seção do programa. O pipeline dentro do microprocessador é redefinido.

De acordo com o princípio de operação, os comandos do microprocessador que fornecem a organização das transições no programa podem ser divididos em 3 grupos:

1. Transferência incondicional de comandos de controle:

1) um comando de ramal incondicional;

2) um comando para chamar um procedimento e retornar de um procedimento;

3) um comando para chamar interrupções de software e retornar das interrupções de software.

2. Comandos para transferência condicional de controle:

1) comandos de salto pelo resultado do comando de comparação p;

2) comandos de transição de acordo com o estado de um determinado sinalizador;

3) instruções para pular o conteúdo do registrador esx/cx.

3. Comandos de controle de ciclo:

1) um comando para organizar um ciclo com um contador ехх/сх;

2) um comando para organizar um ciclo com um contador ех/сх com possibilidade de saída antecipada do ciclo por uma condição adicional.

Saltos incondicionais

A discussão anterior revelou alguns detalhes do mecanismo de transição. As instruções de salto modificam o registrador de ponteiro de instrução eip/ip e possivelmente o registrador de segmento de código cs. O que exatamente precisa ser modificado depende de:

1) sobre o tipo de operando na instrução de desvio incondicional (próximo ou distante);

2) de especificar um modificador antes do endereço de salto (na instrução de salto); neste caso, o próprio endereço de salto pode estar localizado diretamente na instrução (salto direto), ou em um registrador ou célula de memória (salto indireto).

O modificador pode assumir os seguintes valores:

1) near ptr - transição direta para um rótulo dentro do segmento de código atual. Somente o registro eip/ip é modificado (dependendo do tipo de segmento de código use16 ou use32 especificado) com base no endereço (label) especificado no comando ou em uma expressão usando o símbolo de extração de valor - $;

2) far ptr - transição direta para um rótulo em outro segmento de código. O endereço de salto é especificado como um operando ou endereço imediato (rótulo) e consiste em um seletor de 16 bits e um deslocamento de 16/32 bits, que são carregados nos registradores cs e ip/eip, respectivamente;

3) word ptr - transição indireta para um rótulo dentro do segmento de código atual. Apenas eip/ip é modificado (pelo valor de deslocamento da memória no endereço especificado no comando ou de um registrador). Tamanho de deslocamento 16 ou 32 bits;

4) dword ptr - transição indireta para um rótulo em outro segmento de código. Ambos os registradores - cs e eip/ip - são modificados (por um valor da memória - e somente da memória, a partir de um registrador). A primeira palavra/dword deste endereço representa o deslocamento e é carregada em ip/eip; a segunda/terceira palavra é carregada em cs. jmp instrução de salto incondicional

A sintaxe do comando para um salto incondicional é jmp [modificador] jump_address - um salto incondicional sem salvar informações sobre o ponto de retorno.

Jump_address é o endereço na forma de um rótulo ou o endereço da área de memória na qual o ponteiro de salto está localizado.

No total, no sistema de instruções do microprocessador existem vários códigos de instruções de máquina para o salto incondicional jmp.

Suas diferenças são determinadas pela distância de transição e pela forma como o endereço de destino é especificado. A distância do salto é determinada pela localização do operando jump_address. Este endereço pode estar no segmento de código atual ou em algum outro segmento. No primeiro caso, a transição é chamada de intra-segmento, ou próxima, no segundo - inter-segmento, ou distante. Um salto intra-segmento assume que apenas o conteúdo do registrador eip/ip é alterado.

Existem três opções para uso intra-segmento do comando jmp:

1) curto reto;

2) reto;

3) indireto.

Procedimentos

A linguagem assembly possui várias ferramentas que resolvem o problema de duplicar seções de código. Esses incluem:

1) mecanismo de procedimentos;

2) montador de macros;

3) mecanismo de interrupção.

Um procedimento, muitas vezes também chamado de sub-rotina, é a unidade funcional básica para decompor (dividir em várias partes) uma tarefa. Um procedimento é um grupo de comandos para resolver uma subtarefa específica e tem como meio de receber o controle do ponto em que a tarefa é chamada em um nível superior e retornar o controle a esse ponto.

No caso mais simples, o programa pode consistir em um único procedimento. Em outras palavras, um procedimento pode ser definido como um conjunto bem formado de comandos, que, sendo descritos uma vez, podem ser chamados em qualquer lugar do programa, se necessário.

Para descrever uma sequência de comandos como um procedimento em linguagem assembly, duas diretivas são usadas: PROC e ENDP.

A sintaxe da descrição do procedimento é a seguinte (Fig. 36).

Arroz. 36. Sintaxe da descrição do procedimento no programa

A Figura 36 mostra que no cabeçalho do procedimento (diretiva PROC), apenas o nome do procedimento é obrigatório. Dentre o grande número de operandos da diretiva PROC, deve-se destacar [distance]. Esse atributo pode levar os valores próximos ou distantes e caracteriza a possibilidade de chamar o procedimento de outro segmento de código. Por padrão, o atributo [distance] é definido como próximo.

O procedimento pode ser colocado em qualquer lugar do programa, mas de forma que não seja controlado aleatoriamente. Se o procedimento for simplesmente inserido no fluxo de instruções geral, o microprocessador perceberá as instruções do procedimento como parte desse fluxo e, consequentemente, executará as instruções do procedimento.

Saltos condicionais

O microprocessador possui 18 instruções de salto condicional. Esses comandos permitem que você verifique:

1) a relação entre operandos com um signo ("maior - menor");

2) a relação entre operandos sem sinal ("superior - inferior");

3) estados dos sinalizadores aritméticos ZF, SF, CF, OF, PF (mas não AF).

Os comandos de salto condicional têm a mesma sintaxe:

jcc jump_label

Como você pode ver, o código mnemônico de todos os comandos começa com "j" - da palavra jump (jump), ele - determina a condição específica analisada pelo comando.

Quanto ao operando jump_label, este rótulo só pode ser localizado dentro do segmento de código atual; a transferência de controle entre segmentos em saltos condicionais não é permitida. A esse respeito, não há dúvida sobre o modificador, que estava presente na sintaxe dos comandos de salto incondicional. Nos primeiros modelos do microprocessador (i8086, i80186 e i80286), as instruções de desvio condicional só podiam executar saltos curtos - de -128 a +127 bytes da instrução seguinte à instrução de desvio condicional. A partir do modelo de microprocessador 80386, essa restrição é removida, mas, como você pode ver, apenas dentro do segmento de código atual.

Para tomar uma decisão sobre onde o controle será transferido para o comando de salto condicional, uma condição deve primeiro ser formada, com base na qual a decisão de transferir o controle será tomada.

As fontes de tal condição podem ser:

1) qualquer comando que altere o estado dos sinalizadores aritméticos;

2) a instrução de comparação p, que compara os valores de dois operandos;

3) o estado do registro esx/cx.

comando de comparação cmp

O comando de comparação de página tem uma maneira interessante de trabalhar. É exatamente o mesmo que o comando de subtração - sub operando, operando_2.

A instrução p, como a subinstrução, subtrai operandos e define sinalizadores. A única coisa que não faz é escrever o resultado da subtração no lugar do primeiro operando.

A sintaxe de comando str - str operando_1, operando_2 (comparar) - compara dois operandos e define sinalizadores com base nos resultados da comparação.

Os sinalizadores definidos pelo comando p podem ser analisados ​​por instruções especiais de desvio condicional. Antes de analisá-los, vamos prestar um pouco de atenção aos mnemônicos dessas instruções de salto condicional (Tabela 16). Compreender a notação ao formar o nome dos comandos de salto condicional (o elemento no nome do comando jcc, nós o designamos) facilitará sua memorização e posterior uso prático.

Tabela 16. Significado das abreviações no nome do comando jcc Tabela 17. Lista de comandos de salto condicional para o comando p operando_1, operando_2

Não se surpreenda com o fato de que vários códigos mnemônicos diferentes de comandos de desvio condicional correspondem aos mesmos valores de flag (eles são separados uns dos outros por uma barra na Tabela 17). A diferença de nome se deve ao desejo dos desenvolvedores de microprocessadores de facilitar o uso de instruções de salto condicional em combinação com certos grupos de instruções. Portanto, nomes diferentes refletem uma orientação funcional diferente. No entanto, o fato de esses comandos responderem aos mesmos sinalizadores os torna absolutamente equivalentes e iguais no programa. Portanto, na Tabela 17 eles estão agrupados não por nome, mas pelos valores dos sinalizadores (condições) aos quais respondem.

Instruções e sinalizadores de desvio condicional

A designação mnemônica de algumas instruções de salto condicional reflete o nome do sinalizador com o qual eles trabalham e tem a seguinte estrutura: o primeiro caractere é "j" (Saltar, pular), o segundo é a designação do sinalizador ou o caractere de negação " n", seguido do nome do sinalizador . Essa estrutura de equipe reflete seu propósito. Se não houver nenhum caractere "n", o estado do sinalizador será verificado, se for igual a 1, será feita uma transição para o rótulo de salto. Se o caractere "n" estiver presente, o estado do sinalizador será verificado quanto à igualdade com 0 e, se for bem-sucedido, será feito um salto para o rótulo de salto.

Mnemônicos de comando, nomes de sinalizadores e condições de salto são mostrados na Tabela 18. Esses comandos podem ser usados ​​após qualquer comando que modifique os sinalizadores especificados.

Tabela 18. Instruções e sinalizadores de salto condicional

Se você observar atentamente as tabelas 17 e 18, poderá ver que muitas das instruções de salto condicional nelas são equivalentes, pois ambas são baseadas na análise dos mesmos sinalizadores.

Instruções de salto condicional e o registro esx/cx

A arquitetura do microprocessador envolve o uso específico de muitos registradores. Por exemplo, o registrador EAX/AX/AL é usado como acumulador, e os registradores BP, SP são usados ​​para trabalhar com a pilha. O registrador ECX/CX também tem um certo propósito funcional: ele atua como um contador em comandos de controle de loop e ao trabalhar com cadeias de caracteres. É possível que funcionalmente a instrução de desvio condicional associada ao registrador esx/cx seja atribuída mais corretamente a este grupo de instruções.

A sintaxe para esta instrução de desvio condicional é:

1) jcxz jump_label (Saltar se ex for zero) - pular se cx for zero;

2) jecxz jump_label (Jump Equal ех Zero) - salta se ех for zero.

Esses comandos são muito úteis ao fazer loops e ao trabalhar com cadeias de caracteres.

Deve-se notar que há uma limitação inerente ao comando jcxz/jecxz. Ao contrário de outras instruções de transferência condicional, a instrução jcxz/jecxz só pode endereçar saltos curtos -128 bytes ou +127 bytes da instrução seguinte.

Organização de ciclos

O ciclo, como você sabe, é uma estrutura algorítmica importante, sem o uso do qual, provavelmente, nenhum programa pode fazer. Você pode organizar a execução cíclica de uma determinada seção do programa, por exemplo, usando a transferência condicional de comandos de controle ou o comando de salto incondicional jmp. Com esse ciclo de organização, todas as operações para sua organização são realizadas manualmente. Mas, dada a importância de um elemento algorítmico como um ciclo, os desenvolvedores do microprocessador introduziram um grupo de três comandos no sistema de instruções, o que facilita a programação dos ciclos. Essas instruções também usam o registrador esx/cx como um contador de loop.

Vamos dar uma breve descrição desses comandos:

1) loop transaction_label (Loop) - repita o ciclo. O comando permite organizar loops semelhantes aos loops for em linguagens de alto nível com decremento automático do contador de loops. O trabalho da equipe é fazer o seguinte:

a) decremento do registro ECX/CX;

b) comparação do registrador ECX/CX com zero: se (ECX/CX) = 0, então o controle é transferido para o próximo comando após a malha;

2) loope/loopz jump_label

Os comandos loope e loopz são sinônimos absolutos. O trabalho dos comandos é executar as seguintes ações:

a) decremento do registro ECX/CX;

b) comparação do registro ECX/CX com zero;

c) análise do estado do sinalizador zero ZF se (ECX/CX) = 0 ou XF = 0, o controle é transferido para o próximo comando após a malha.

3) loopne/loopnz jump_label

Os comandos loopne e loopnz também são sinônimos absolutos. O trabalho dos comandos é executar as seguintes ações:

a) decremento do registro ECX/CX;

b) comparação do registro ECX/CX com zero;

c) análise do estado da bandeira zero ZF: se (ECX/CX) = 0 ou ZF = 1, o controle é transferido para o próximo comando após a malha.

Os comandos loope/loopz e loopne/loopnz são recíprocos em sua operação. Eles estendem a ação do comando loop analisando adicionalmente o sinalizador zf, o que torna possível organizar uma saída antecipada do loop, usando esse sinalizador como indicador.

A desvantagem dos comandos de loop loop, loope/loopz e loopne/loopnz é que eles implementam apenas saltos curtos (de -128 a +127 bytes). Para trabalhar com loops longos, você precisará usar saltos condicionais e a instrução jmp, então tente dominar as duas formas de organizar loops.

Autor: Tsvetkova A.V.

<< Voltar: Equipes (Comandos de transferência de dados. Comandos aritméticos)

Recomendamos artigos interessantes seção Notas de aula, folhas de dicas:

Notários. Berço

Lei romana. Notas de aula

Terapia da Faculdade. Berço

Veja outros artigos seção Notas de aula, folhas de dicas.

Leia e escreva útil comentários sobre este artigo.

<< Voltar

Últimas notícias de ciência e tecnologia, nova eletrônica:

A existência de uma regra de entropia para o emaranhamento quântico foi comprovada 09.05.2024

A mecânica quântica continua a nos surpreender com seus fenômenos misteriosos e descobertas inesperadas. Recentemente, Bartosz Regula do Centro RIKEN de Computação Quântica e Ludovico Lamy da Universidade de Amsterdã apresentaram uma nova descoberta que diz respeito ao emaranhamento quântico e sua relação com a entropia. O emaranhamento quântico desempenha um papel importante na moderna ciência e tecnologia da informação quântica. No entanto, a complexidade da sua estrutura torna a sua compreensão e gestão um desafio. A descoberta de Regulus e Lamy mostra que o emaranhamento quântico segue uma regra de entropia semelhante à dos sistemas clássicos. Esta descoberta abre novas perspectivas na ciência e tecnologia da informação quântica, aprofundando a nossa compreensão do emaranhamento quântico e a sua ligação à termodinâmica. Os resultados do estudo indicam a possibilidade de reversibilidade das transformações de emaranhamento, o que poderia simplificar muito seu uso em diversas tecnologias quânticas. Abrindo uma nova regra ... >>

Mini ar condicionado Sony Reon Pocket 5 09.05.2024

O verão é uma época de relaxamento e viagens, mas muitas vezes o calor pode transformar essa época em um tormento insuportável. Conheça um novo produto da Sony – o minicondicionador Reon Pocket 5, que promete deixar o verão mais confortável para seus usuários. A Sony lançou um dispositivo exclusivo - o minicondicionador Reon Pocket 5, que fornece resfriamento corporal em dias quentes. Com ele, os usuários podem desfrutar do frescor a qualquer hora e em qualquer lugar, simplesmente usando-o no pescoço. Este miniar condicionado está equipado com ajuste automático dos modos de operação, além de sensores de temperatura e umidade. Graças a tecnologias inovadoras, o Reon Pocket 5 ajusta o seu funcionamento em função da atividade do utilizador e das condições ambientais. Os usuários podem ajustar facilmente a temperatura usando um aplicativo móvel dedicado conectado via Bluetooth. Além disso, camisetas e shorts especialmente desenhados estão disponíveis para maior comodidade, aos quais um mini ar condicionado pode ser acoplado. O dispositivo pode, oh ... >>

Energia do espaço para Starship 08.05.2024

A produção de energia solar no espaço está se tornando mais viável com o advento de novas tecnologias e o desenvolvimento de programas espaciais. O chefe da startup Virtus Solis compartilhou sua visão de usar a Starship da SpaceX para criar usinas orbitais capazes de abastecer a Terra. A startup Virtus Solis revelou um ambicioso projeto para criar usinas de energia orbitais usando a Starship da SpaceX. Esta ideia poderia mudar significativamente o campo da produção de energia solar, tornando-a mais acessível e barata. O cerne do plano da startup é reduzir o custo de lançamento de satélites ao espaço usando a Starship. Espera-se que este avanço tecnológico torne a produção de energia solar no espaço mais competitiva com as fontes de energia tradicionais. A Virtual Solis planeja construir grandes painéis fotovoltaicos em órbita, usando a Starship para entregar os equipamentos necessários. Contudo, um dos principais desafios ... >>

Notícias aleatórias do Arquivo

Minielétrico 11.07.2019

A montadora alemã BMW lançou um carro elétrico de série Mini Electric (Mini Cooper SE). A novidade é baseada na versão ICE do modelo, portanto o design, dimensões, design de interiores e outras características são quase idênticas (com algumas exceções). Por exemplo, a altura de um carro elétrico é apenas 18 mm mais alta e o peso é apenas 145 kg a mais.

O Mini Electric de três portas e tração dianteira recebeu um motor elétrico projetado pela BMW com 135 kW (184 cv) e 270 Nm de torque. A aceleração de 0 a 60 km/h leva 3,9 segundos, de 0 a 100 km/h - 7,3 segundos, a velocidade máxima é limitada a 150 km/h.

O motorista tem quatro modos de eficiência (Standard, Sport, GREEN e GREEN+) e dois níveis de configurações de recuperação que permitem escolher um modo de condução em que apenas um pedal do acelerador é suficiente para controlar a dinâmica.

A bateria em forma de T é integrada ao piso (localizada entre os bancos dianteiros e sob os bancos traseiros, o que possibilitou economizar espaço para as pernas), é montada a partir de 12 módulos com capacidade total de 32,6 kWh.

Uma carga completa da bateria é suficiente para percorrer de 235 a 270 km (WLTP), dependendo do estilo de condução e das condições da estrada. A bateria suporta carregamento de alta velocidade até 50 kW, neste modo, 80% da capacidade é adquirida em 35 minutos. O conector de carregamento está localizado acima da roda traseira direita.

O equipamento padrão inclui faróis de LED, painel de instrumentos digital de 5,5 polegadas, ar condicionado de duas zonas, aquecimento interno baseado em bomba de calor, freio de estacionamento elétrico e Navegação Conectada de 6,5 polegadas (Pacote de Navegação Conectada disponível como opção). tela polegadas).

O Mini Electric será montado em novembro deste ano no Reino Unido, na mesma fábrica em Oxford onde é montada a versão ICE. O custo de um carro elétrico, levando em conta os benefícios, começa em US$ 30400, as primeiras entregas aos clientes ocorrerão no início de 2020.

Outras notícias interessantes:

▪ Novo processador Novo processador Pentium E6300 dual-core da Intel

▪ Refrigerador de termossifão Vortex - uma nova fonte de energia

▪ Drones para resgatar pessoas que se afogam

▪ Cartões Plásticos

▪ Dormir antes de dormir melhora a memória

Feed de notícias de ciência e tecnologia, nova eletrônica

 

Materiais interessantes da Biblioteca Técnica Gratuita:

▪ seção do site Fatos interessantes. Seleção de artigos

▪ artigo Fuga de cérebros, fuga de cérebros. expressão popular

▪ artigo Como as responsabilidades são distribuídas em uma família de formigas? Resposta detalhada

▪ artigo Carregadores de bateria. Diretório

▪ artigo Antena Isotron 80/40 Combo. Enciclopédia de rádio eletrônica e engenharia elétrica

▪ artigo Sempre 100. O segredo do foco

Deixe seu comentário neste artigo:

Имя:


E-mail opcional):


Comentário:





Todos os idiomas desta página

Página principal | Biblioteca | Artigos | Mapa do Site | Revisões do site

www.diagrama.com.ua

www.diagrama.com.ua
2000-2024