Informática Numaboa - Tutoriais e Programação
Flags e saltos condicionais
Sab 20 Jun 2009 19:05 |
- Detalhes
- Categoria: Assembly Numaboa (antigo oiciliS)
- Atualização: Domingo, 21 Junho 2009 16:04
- Autor: vovó Vicki
- Acessos: 17195
A tradução literal de "flag" é bandeira. Não foi por acaso que as "flags" do processador receberam este nome: funcionam como sinalizadores. Vou manter o nome flag e não usar algum tipo de tradução por que esta denominação já foi incorporada ao "computês" do Brasil e não vale a pena discutir ou mudar um hábito. Neste tutorial, aprenda a usar as flags para criar saltos condicionais.
As flags são apenas um único bit de memória localizado dentro do processador. Como cada flag é apenas um bit, num dado momento elas só podem ter o valor 1 ou 0 (flag "setada" ou "zerada"). Existem flags que podem ser usadas para indicar o resultado de certas instruções. Algumas instruções como CMP, TEST e BT não fazem outra coisa a não ser alterar algumas destas flags, outras realizam tarefas adicionais além de alterar algumas das flags. Também existem instruções que simplesmente não alteram as flags.
Um uso comum das flags é o de desviar a execução para um ponto em particular do código usando instruções de salto condicinal. Estas instruções farão o salto (ou não) dependendo do estado de uma ou de mais flags. Apenas cinco das flags podem ser usadas deste modo - as flags zero, sinal, carry, overflow e paridade. A sexta flag (carry auxiliar) e a sétima (flag de direção) são lidas por outro tipo de instrução. A seguir estão informações adicionais a respeito das cinco flags que podem ser usadas por saltos condicionais.
Z (flag zero)
Está setada (tem o valor 1) se o resultado de uma operação for zero. Depois de uma instrução aritmética, se o número deixado no registrador ou na a área de memória objeto da instrução for zero, então a flag é setada. Para obter esta informação geralmente só é preciso fazer uma simples comparação de dois valores sem alterá-los. Neste caso, pode-se usar a instrução CMP. CMP imita um SUB sem alterar os valores passados como operandos. Por exemplo:
A flag zero também pode ser usada para mostrar o resultado de uma contagem crescente ou decrescente, por exemplo:
A flag zero também pode ser usada para controlar a repetição de instruções de string, isto é, LODS, STOS e MOVS:
As versões de 16 e 8 bits das instruções testam apenas os primeiros 16 ou 8 bits do registrador ou da área de memória. Por exemplo:
Já que as flags são muito úteis para indicar se houve retorno com sucesso ou não de uma rotina, algumas vezes será preciso setá-las diretamente. Para setar a flag zero pode-se usar:
Quando usada com TEST, a flag zero será setada se o bit testado for zero.
A flag zero é usada principalmente com as instruções de salto condicional JZ (saltar se for zero) e JNZ (saltar se não for zero) , por exemplo:
A flag zero também é utilizada com as instruções de salto condicional JA ("jump if above" - salte se acima), JB ("jump if below" - salte se abaixo) e instruções semelhantes.
Também pode ser usada em um loop utilizando instruções especiais ou apenas a flag, por exemplo:
S (flag de sinal)
Esta flag está setada quando o bit mais significativo (o bit mais à esquerda) do resultado for 1. A posição deste bit depende do tamanho do dado. Num byte, o bit mais significativo é o bit 7 (8° bit dos bits 0 a 7); num word é o bit 15 (16° bit dos bits 0 a 15) e num dword é o bit 31 (32° bit dos bits 0 a 31). Este bit estará setado se o resultado da instrução for 80h ou mais (para um byte), 8000h ou mais (para um word) ou 80000000h ou mais (para um dword). Lembre-se que, em números com sinal, o bit mais significativo indica se o número é negativo ou não.
A flag de sinal é alterada por INC e DEC, instruções que não alteram a flag de carry. Por isto pode ser muito útil testar a flag de sinal em loops. Por exemplo:
Seu uso também é muito conveniente em funções de multi-ação, por exemplo:
A flag de sinal também é um jeito muito prático de ver se o bit alto de um registrador está setado ou zerado. Várias instruções setam a flag sem alterar o registrador, por exemplo:
Ao checar áreas de memória, pode-se endereçar a memória apenas uma vez por instrução. Desta forma, é preciso usar CMP, como por exemplo:
Observe que a posição do bit mais alto na área de memória chamada de DATA44, usada nestas instruções, depende do tamanho do dado usado na instrução. Isto é porque dados em áreas de memória são armazenados com os bytes em ordem reversa, ou seja, o byte menos significativo primeiro e o mais significativo por último. Veja Little Endian. A instrução CMP B[DATA44+7],0 analisa o 8° byte que contém o 64° bit. Este é o sinal, mas apenas para um dado de 64 bits de tamanho.
A flag de sinal é usada principalmente com as instruções de salto condicional JS e JNS, por exemplo:
A flag de sinal também é usada com as instruções de salto condicional JG ("jump if greater-than" - salte se maior que), JNG ("jump if not greater-than" - salte se não maior que) e semelhantes.
C (flag de carry)
Esta flag é setada quando o resultado da instrução estourou o limite do tamanho do dado, isto é, houve uma transposição ("carry"), o famoso "vai-um". Imagine, por exemplo, que numa instrução de 8 bits o valor 1 é adicionado a 255. O resultado NÃO pode ser 256 por que 255 é o valor máximo que pode estar contido num byte de 8 bits. O resultado será 0, mas a flag de carry será setada. Imagine também que numa instrução o valor 4 é subtraído de 2. Novamente isto faz com que a flag de carry seja setada (tenha valor 1) por que o resultado caiu abaixo de zero, que é o limite inferior do tamanho do dado.
A flag de carry, portanto, indica que uma transposição ou estouro (overflow) ocorreu quando se estava usando números sem sinal. Veja a flag de overflow para encontrar estouros quando são usados números com sinal.
Diferentemente de outras flags, existem instruções criadas para manipular a flag de carry diretamente.
Como estas instruções são muito simples, a flag de carry é muito útil para levar o resultado de uma função ao seu chamador, por exemplo:
As instruções INC e DEC não alteram a flag de carry. As instruções de loop também não. Isto é útil quando se tem um loop que precisa informar seu resultado. Neste caso também podemos usar a flag de carry, como por exemplo:
Existem algumas instruções que sempre zeram a flag de carry, o que é bom saber para evitar o uso desnecessário da instrução CLC. Estas instruções são AND, OR e TEST. Algumas instruções respondem a um input da flag de carry; outras fazem seu output na flag de carry. A flag de carry é usada principalmente com as instruções de salto condicional JC e JNC, por exemplo:
A flag de carry também é utilizada com as instruções de salto condicional JA ("jump if above" - salta se acima), JB ("jump if below" - salta se abaixo) e similares.
O (flag de overflow)
Overflow significa transbordar, inundar. No "computês" do Brasil costuma-se dizer "estourar". Para entender a flag de overflow é preciso que fique bem claro o que são números com sinal. A flag de overflow é usada para indicar um estouro quando números com sinal são usados. A flag de carry não pode ser usada para este fim. Um simples exemplo é suficiente para provar:
Neste caso a flag de carry será setada por que o resultado sem sinal 258 ultrapassa o limite de 255 que corresponde ao tamanho do dado. Porém, se este for um cálculo com sinal, não haverá estouro: -2 + 4 = 2, al contém o resultado correto de 2 e a flag de overflow permanece zerada.
Um outro exemplo onde há um overflow num cálculo com sinal:
Neste outro caso a flag de carry está zerada por que o resultado sem sinal 131 está dentro do limite de 255 do tamanho do dado. Porém, em relação ao cálculo com sinal, houve um estouro por que, se al contém 83h, este é o número decimal com sinal -125, um resultado errado. O resultado correto 131 fica fora dos limites -127 a +128 dos números com sinal de 8 bits.
Neste tipo de operação aritmética o processador seta a flag de overflow se o bit de sinal muda sem que tenha havido um "carry". Observe como isto ocorre independente da flag de carry:
Nas instruções de deslocamento (shift), apenas nas operações de um único shift pode-se esperar que a flag de overflow dê uma indicação válida se um resultado com sinal for muito grande para o tamanho do dado. Por exemplo:
SAR é uma instrução especial de deslocamento com sinal à direita que mantém o sinal correto no resultado. Ela consegue fazer isto deslocando todos os bits, exceto o bit mais alto. Como um shift SAR único na realidade é uma divisão por +2, nunca poderá ocorrer um overflow. Já a instrução SHL pode e, em operações de um shift único, a flag de overflow será devidamente setada. Para isto, o processador faz um teste para ver se o bit de sinal é o mesmo que a flag de carry, zerando a flag de overflow se seus valores forem idênticos. Devido a este teste é possível estabelecer um outro uso para a flag de overflow (estes testes mudam o conteúdo do registrador):
Instruções de rotação funcionam do mesmo modo que as de deslocamento. Como a instrução ROR desloca todos os bits para a direita substituindo o bit mais alto pelo mais baixo, isto possibilita comparar o bit mais alto com o bit mais baixo de dados. Por exemplo (estes testes mudam o conteúdo do registrador):
A instrução especial de multiplicação com sinal IMUL seta a flag de overflow se o resultado com sinal for maior que o tamanho do dado.
A flag de overflow é usada principalmente com as instruções de salto condicional JO e JNO, por exemplo:
P (flag de paridade)
A flag de paridade indica se existe um número par ou ímpar de bits setados no dado. Ela estará setada se o número de bits setados for par e zerada se o número for ímpar. Na comunicação serial, o bit de paridade é usado como um checador de erros pouco sofisticado. Após cada byte enviado, o transmissor envia um bit de paridade que indica ao receptor se o byte que acabou de ser enviado deveria ter um número par ou ímpar de bits setados. Este sistema pode deixar escapar um byte corrompido, mas geralmente detecta uma série de bytes corrompidos. Quando usado desta forma, um byte pode ter menos do que 8 bits: transmissões seriais geralmente usam bytes de 7 bits mais um bit de paridade.
A flag de paridade geralmente é usada com as instruções de salto condicional JP e JNP, por exemplo: