Tutorial do assembler 8086 para principiantes (parte 3) Variáveis Variável é um local na memória. Para o programador é muito mais fácil ter um valor armazenado em uma variável chamada "var1" do que em um endereço 5A73:235B, especialmente quando se tem 10 variáveis ou mais. Nosso compilador suporta dois tipos de variáveis: BYTE e WORD. Sintaxe para a declaração de variáveis: nome DB valor nome DW valor DB - representa Define Byte. DW - representa Define Word. nome – pode conter qualquer letra ou números, porém deve iniciar com uma letra. É possível declarar variáveis sem especificar um nome na declaração (essa variável terá um endereço mas não terá nome). valor – deverá conter somente números em qualquer sistema numérico suportado (hexadecimal, binário, ou decimal), ou o símbolo "?" para variáveis não inicializadas. Como você provavelmente já conhece da parte 2 deste tutorial, a instrução MOV é usada para copiar valores de uma origem para um destino. Vamos ver outro exemplo com a instrução MOV: ORG 100h MOV AL, var1 MOV BX, var2 RET ; encerra o programa. VAR1 DB 7 var2 DW 1234h Copie o código acima e cole no editor de códigos, e tecle F5 para compilar e carregar o programa no emulador. Você deverá ver algo como: Como você pode ver isso parece muito com o nosso exemplo, exceto que as variáveis são substituídas pelo seu endereço real na memória. Quando o compilador gera o código de máquina, ele substitui todos os nomes de variáveis pelos seus offsets. Por padrão o segmento é carregado no registro DS (Quando arquivos COM são carregados é atribuído ao valor do registro DS o mesmo valor do registro CS - code segment). Na listagem de memória a primeira coluna é o offset, a segunda coluna é o valor hexadecimal, a terceira coluna é o valor decimal, e a ultima coluna é o valor ASCII representado por um caractere. O compilador não é case sensitive (diferenciar maiúsculas de minúsculas), logo "VAR1" e "var1" referem-se a mesma variável. O offset de VAR1 é 0108h, e o endereço completo é 0B56:0108. O offset de var2 é 0109h, e o endereço completo é 0B56:0109, Essa variável é uma WORD logo ocupa 2 BYTES. É assumido que a parte mais baixa é armazenada no endereço mais baixo, então 34h é localizado antes de 12h. Como você pode ver existem algumas instruções depois da instrução RET, Isso acontece por que o disassembler não tem idéia de onde começam os dados, ele apenas processa os valores na memória e entende como instruções 8086 válidas (nós aprenderemos sobre elas mais tarde). Você pode até escrever o mesmo programa usando somente diretivas DB: ORG 100h DB 0A0h DB 08h DB 01h DB DB DB DB 8Bh 1Eh 09h 01h DB 0C3h DB 7 DB 34h DB 12h Copie o código acima para o editor de códigos, e tecle F5 para compilar e carregar o programa no emulador. Você deverá ver o mesmo código desmontado (disassembled), e a mesma funcionalidade! Como você deve estar imaginando, o compilador apenas converte o código fonte em uma série de bytes, que é chamado de código de máquina, o processador entende o código de máquina e o executa. ORG 100h é uma diretiva do compilador (ela diz ao compilador como ele deve manejar o código fonte). Essa diretiva é muito importante quando trabalhamos com variáveis. Ela diz ao compilador que o arquivo executável será carregado no offset 100h (256 bytes), então o compilador irá calcular corretamente o endereço para todas as variáveis quando substituir seus nomes por seus offsets. As diretivas nunca são convertidas para nenhum código de máquina real. Por que os arquivos executáveis são carregados no offset 100h? O sistema operacional mantém algumas informações sobre o programa nos primeiros 256 bytes do CS (code segment), tais como parâmetros informados na linha de comando e etc. Mas isso só é verdadeiro para arquivo COM, Arquivos EXE são carregados no offset 0000, e geralmente usam segmentos especiais para variáveis. Talvez nós falaremos mais sobre arquivos EXE mais tarde. Arrays (vetores) Arrays podem ser vistos como correntes de variáveis. Uma string de texto é um exemplo de array de bytes, cada caractere é representado por seu valor ASCII (0..255). Aqui estão alguns exemplos de definição de arrays: a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h b DB 'Hello', 0 b é uma cópia exata do array a, quando o compilador vê a string entre aspas ele automaticamente a converte para um conjunto de bytes. A tabela abaixo mostra a parte da memória onde esses arrays estão declarados: Você pode acessar o valor de qualquer elemento no array usando colchetes, por exemplo: MOV AL, a[3] Você também pode usar qualquer registro de índice de memória BX, SI, DI, BP, por exemplo: MOV SI, 3 MOV AL, a[SI] Se você precisar declarar um array grande, você pode usar o operador DUP. Sintaxe para o operador DUP: numero DUP ( valor(es) ) numero – quantidade de replicações (duplicate) a fazer (qualquer valor constante). valor – expressão que o DUP vai duplicar. por exemplo: c DB 5 DUP(9) É uma maneira alternativa de declarar: c DB 9, 9, 9, 9, 9 outro exemplo: d DB 5 DUP(1, 2) É uma maneira alternativa de declarar: d DB 1, 2, 1, 2, 1, 2, 1, 2, 1, 2 Logicamente você pode usar DW em vez de DB se for preciso armazenar valores maiores que 255, ou menores que -128. O DW não pode ser usado para declarar strings. Obtendo o Endereço de Uma Variável A instrução LEA (Load Effective Address ou carregar o endereço real) e o operador alternativo OFFSET. Ambos, OFFSET e LEA, podem ser usados para carregar o endereço offset de uma variável. A instrução LEA é mais poderosa por que permite que você obtenha o endereço de variáveis indexadas. Obter o endereço de uma variável pode ser muito útil em algumas situações, por exemplo quando você precisa passar parâmetros para uma procedure. Lembrete: Para informar ao compilador qual o tipo de dados, os seguintes prefixos devem ser usados: BYTE PTR - para byte. WORD PTR - para word (dois bytes). Por exemplo: BYTE PTR [BX] ; acesso à byte. ou WORD PTR [BX] ; acesso à word. O assembler também suporta abreviações dos prefixos: b. - para BYTE PTR w. - para WORD PTR em certos casos o assembler pode calcular o tipo de dados automaticamente. Aqui está o primeiro exemplo: ORG 100h MOV AL, VAR1 ; checa o valor de VAR1 copiando-o para AL. LEA BX, VAR1 ; guarda o endereço da variável VAR1 em BX. MOV BYTE PTR [BX], 44h ; modifica o conteúdo de VAR1. MOV AL, VAR1 ; checa o valor de VAR1 copiando-o para AL. RET VAR1 DB 22h END Aqui está outro exemplo, que usa OFFSET em vez de LEA: ORG 100h MOV AL, VAR1 ; checa o valor de VAR1 copiando-o para AL. MOV BX, OFFSET VAR1 ; guarda o endereço da variável VAR1 em BX. MOV BYTE PTR [BX], 44h ; modifica o conteúdo de VAR1. MOV AL, VAR1 ; checa o valor de VAR1 copiando-o para AL. RET VAR1 DB 22h END Ambos os exemplos têm a mesma funcionalidade. As linhas: LEA BX, VAR1 MOV BX, OFFSET VAR1 são até mesmo compiladas em um mesmo código de máquina: MOV BX, num num é um valor de uma variável offset de 16 bits. Note que você só pode usar colchetes (como ponteiro de memória) para os registros: BX, SI, DI, BP! (veja a parte anterior do tutorial). Constantes Constantes são como variáveis, porém elas só existem até que o programa seja compilado (assembled). Após a definição de uma constante o seu valor não pode ser modificado. Para definir uma constante a diretiva EQU é usada: nome EQU < qualquer expressão > Por exemplo: k EQU 5 MOV AX, k O exemplo acima é funcionalmente idêntico ao código: MOV AX, 5 Voce pode visualizar as variáveis enquanto seu programa é executado selecionando "Variables" no menu "View" do emulador. Para vizualizar arrays você deve clicar sobre a variável e selecionar a propriedade Elements para o tamanho do array (array size). Na linguagem assembly não existem tipos restritos de dados, então qualquer variável pode ser apresentada como um array. As variáveis podem ser visualizadas em qualquer um dos seguintes sistemas numéricos: HEX - hexadecimal (base 16). BIN - binario (base 2). OCT - octal (base 8). SIGNED - decimal com sinal (base 10). UNSIGNED - decimal sem sinal (base 10). CHAR – caractere ASCII (existem 256 símbolos, alguns símbolos são invisíveis). Você pode editar o valor de uma variável em tempo de execução, é só dar um duplo clique na variável, ou selecionar e clicar no botão Edit. É possível digitar os números em qualquer sistema de numeração, números hexadecimais devem ter o sufixo "h", binários o sufixo "b", octal o sufixo "o", os números decimais não precisam de sufixo. Strings podem ser digitadas desta maneira: 'hello world', 0 (esta string é terminada com um zero). Arrays devem ser digitados desta maneira: 1, 2, 3, 4, 5 (O array pode ser um array de bytes ou words, vai depender se a variável selecionada para edição é do tipo BYTE ou WORD). Expressões são automaticamente convertidas, por exemplo: quando esta expressão é digitada: 5+2 vai ser convertida para 7 e etc.