Informática Numaboa - Tutoriais e Programação

Flags e saltos condicionais

Sab

20

Jun

2009


19:05

  • Imprimir
(7 votos, média 5.00 de 5) 


Nível Intermediário 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:

CMP EAX,33h ; flag zero = 1 se eax = 33h, mas não altera eax SUB EAX,33h ; flag zero = 1 se eax = 33h (eax diminui em 33h) CMP EAX,EDX ; flag zero = 1 se eax = edx CMP EAX,[VALOR] ; flag zero = 1 se eax = o número em VALOR

A flag zero também pode ser usada para mostrar o resultado de uma contagem crescente ou decrescente, por exemplo:

DEC EAX ; flag zero = 1 se eax for zero depois da instrução. ; flag zero = 0 se eax for diferente de zero. INC EAX ; flag zero = 1 se eax for zero depois da instrução. ; flag zero = 0 se eax for diferente de zero.

A flag zero também pode ser usada para controlar a repetição de instruções de string, isto é, LODS, STOS e MOVS:

CMP EAX,0 ; ver se o número em eax é zero ; (flag zero está setada) OR EAX,EAX ; faz a mesma coisa mas usa 2 opcodes ao invés de 3 TEST EAX,EAX ; faz a mesma coisa e também só usa 2 opcodes

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:

CMP W[JOAO],0 ; ver se os primeiros 16 bits da memória marcada ; como JOAO são zero CMP B[MARIA],0 ; ver se os primeiros 8 bits da memória marcada ; como MARIA são zero OR DX,DX ; ver se o registrador dx é zero (16 bits) TEST DH,DH ; ver se o registrador dh é zero (8 bits) SUB B[VALOR],2 ; reduz os 8 bits guardados em VALOR em 2 ; (flag zero setada se for zero) DEC SI ; flag zero setada quando si for zero (16 bits) INC B[CONTA] ; flag zero setada quando os 8 bits de CONTA são zero

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:

CMP EAX,EAX ; seta a flag zero sem alterar eax SUB EAX,EAX ; seta a flag zero fazendo eax = 0 CMP EAX,EDX ; quando precisam ser diferentes, zera a flag zero OR EAX,EAX ; quando eax não pode ser zero, zera a flag zero TEST EAX,EAX ; mesmo efeito que OR EAX,EAX

Quando usada com TEST, a flag zero será setada se o bit testado for zero.

MOV ECX,1 ; põe valor 1 em ecx TEST ECX,1 ; a flag zero não é setada CMP ECX,1 ; a flag zero é setada MOV EDX,0 TEST EDX,1 ; a flag zero é setada CMP EDX,1 ; a flag zero não é setada MOV EBX,-1 TEST EBX,-1 ; testa todos os 32 bits (flag zero zerada) CMP EBX,-1 ; ver se ebx é -1 (flag zero setada)

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:

JZ >L10 ; salta para frente para L10 se flag zero = 1 JNZ L1 ; salta para trás para L1 se flag zero = 0

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:

L1: ... ; outro código aqui CMP EDX,EAX LOOPZ L1 ; decrementa ecx, loop continua até que ecx = 0 ; ou até que edx = eax (quando flag zero = 1) L1: ... ; outro código aqui CMP EDX,EAX LOOPNZ L1 ; decrementa ecx, loop continua até que ecx = 0 ; ou até que edx seja diferente de eax (quando flag zero = 0) L1: ... ; outro código aqui CMP EDX,EAX JZ >L10 ; salte para fora do loop quando edx = eax (flag zero = 1) LOOP L1 ; decrementa ecx, loop continua até que ecx = 0 L10: L1: ... ; outro código aqui CMP EDX,EAX JNZ L1 ; loop continua até que edx = eax (flag zero = 1)

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:

L0: ; DEC ECX ; reduz ecx em um JNS L0 ; volta para L0 se ecx ainda não for -1

Seu uso também é muito conveniente em funções de multi-ação, por exemplo:

MULTI_ACAO: ; na entrada, al guarda a ação desejada DEC AL ; ver se al contém zero JS >L0 ; se sim, saltar para L0 DEC AL ; ver se al contém um JS >L1 ; se sim, saltar para L1 DEC AL ; ver se al contém dois JS >L2 ; se sim, saltar para L2 DEC AL ; ver se al contém três JS >L3 ; se sim, saltar para L3 DEC AL ; ver se al contém quatro JS >L4 ; se sim, saltar para L4

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:

OR EDX,EDX ; seta a flag de sinal se o bit alto de edx estiver setado CMP EDX,EDX ; - idem - TEST EDX,EDX ; - idem - OR CL,CL ; seta a flag de sinal se o bit alto de cl estiver setado CMP CL,CL ; - idem - TEST CL,CL ; - idem -

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:

CMP B[DATA44],0 ; seta a flag de sinal se o 8° bit de DATA44 estiver setado CMP W[DATA44],0 ; seta a flag de sinal se o 16° bit de DATA44 estiver setado CMP D[DATA44],0 ; seta a flag de sinal se o 32° bit de DATA44 estiver setado CMP B[DATA44+7],0 ; seta a flag de sinal se o 64° bit de DATA44 estiver setado CMP B[DATA44+9],0 ; seta a flag de sinal se o 80° bit de DATA44 estiver setado

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:

JS >L10 ; salta para frente para L10 se a flag de sinal = 1 JNS L1 ; salta para trás para L1 se a flag de sinal = 0

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.

STC ; seta a flag de carry CLC ; zera a flag de carry CMC ; complementa a flag de carry

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:

CALCULA2: ; CMP EBX,ESI JZ >.falhou ; salta para falhou se ebx = esi CMP EAX,ESI JZ >.sucesso ; salta para sucesso se eax = esi .falhou STC ; seta a flag de carry para mostrar falha RET .sucesso CLC ; zera a flag de carry para indicar sucesso RET ; CALCULA1: CALL CALCULA2 JC >L40 ; salta para frente para L40 se falhou

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:

.loop ; CMP ESI,EDI ; ver se esi é menor que edi (seta carry se for) DEC ECX ; ver se mais loops precisam ser feitos JNZ .loop ; sim RET ; returna com o resultado de cmp esi,edi na flag de carry

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:

JC >L10 ; salta para frente para L10 se a carry estiver setada JNC L1 ; salta para trás para L1 se a carry estiver zerada

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:

MOV AL,0FEh ; põe 254 decimal (sem sinal) ou -2 (com sinal) em al ADD AL,4h ; adiciona 4. al agora está com 2h

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:

MOV AL,7Fh ; põe 127 decimal em al ADD AL,4h ; adiciona 4. al agora está com 83h

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:

MOV AL,7Fh ; põe 127 decimal em al INC AL ; causa overflow (carry não é afetada por INC) ; MOV AL,80h ; põe -128 decimal em al DEC AL ; causa overflow (carry não é afetada por DEC)

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:

MOV AL,80h ; põe -128 em al SHL AL,1 ; multiplicar por 2 causa overflow MOV AL,0FEh ; põe -2 em al SHL AL,1 ; multiplicar por 2 resulta em -4 sem overflow MOV AL,80h ; põe -128 em al SHL AL,2 ; multiplica por 4 sem indicação de overflow MOV AL,0FEh ; põe -2 em al SAR AL,1 ; divisão por 2 resulta em -1 sem overflow

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):

SHL AL,1 JNO >L1 ; salte se os dois bits mais altos de al forem iguais SHL AL,1 JO >L1 ; salte se os dois bits mais altos de al forem diferentes SHL EAX,1 JNO >L1 ; salte se os dois bits mais altos de ax forem iguais SHL EAX,1 JO >L1 ; salte se os dois bits mais altos de eax forem diferentes

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):

ROR AL,1 JNO >L1 ; salte se o bit mais alto e o mais baixo de al forem iguais ROR AL,1 JO >L1 ; salte se o bit mais alto e o mais baixo de al forem diferentes ROR EAX,1 JNO >L1 ; salte se o bit mais alto e o mais baixo de eax forem iguais ROR EAX,1 JO >L1 ; salte se o bit mais alto e o mais baixo de eax forem diferentes

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:

JO >L10 ; salte para L10 se a flag de overflow estiver setada JNO L1 ; salte para L1 se a flag de overflow estiver zerada

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:

JP >L10 ; salte para L10 se a flag de paridade estiver setada JNP L1 ; salte para L1 se a flag de paridade estiver zeradaкомпромат Вадим Логофет посуда для русской печиооо отзывы полигон отзывы topodinресторан nikas отзывы сегодня харьков погодаадреса никас