Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Para convertir una letra de mayúscula a minúscula , notamos que el código ASCII para las letras mayúsculas de 'A'
a 'Z' forman una secuencia entre 65 a 90.
Las correspondientes letras minúsculas de 'a' a 'z' estan contenidas en la secuencia entre 97 y 122.
Nosotros decimos que el código Ascii forma una secuencia ordenada y usamos este hecho para ordenar
textualmente información.
Para convertir un carácter de mayúscula a su equivalente en minúscula, nosotros sumamos 32 al código Ascii de la
letra mayúscula.
Para convertir de mayúscula a minúscula, nosotros substraemos 32 del codigo Ascii de la
letra mayúscula.
El número 32 es obtenido substrayendo el código Ascii de 'A' del codigo Ascii de 'a'
(i.e. 'A'-'a' = 97-65=32).
Ejemplo 3,19: Escriba un programa que pida al usuario que ingrese una letra mayúscula, lea la letra y muestre a
pantalla su correspondiente letra minúscula. El programa debe convertir la letra a su minúscula correspondiente y mostrarla
en una nueva linea de pantalla.
CR equ 13d
LF equ 10d
.data
msg1 db ‘Enter an uppercase letter: $’
result db CR, LF, ‘The lowercase equivalent is: $’
.code
; main program
start:
end start
El string result esta definido para comenzar con el Return y el saldo de línea que harán que se muestre en una
nueva línea.
Una alternativa puede ser incluir los dos caracteres al final del string msg1, antes del carácter '$', por ejemplo:
Despues de mostrar msg1, como esta definido anteriormente, el próximo iten a ser mostrado aparecerá en una
nueva línea.
Ejercicios:
3.11Modifique el ejercicion anterior para que convierta letras minúsculas a sus respectivas mayúsculas.
3.12Escriba un programa para que convierta un número simples como un 5 a su carácter equivalente '5', y muestre
en pantalla.
Esto significa que debemos ser cuidadosos en recordar a cual registro (al,dl,dx) nosotros pasamos el parámetro.
Un enfoque más consistente puede ser usar el mismo registro para pasar el parametro a todos los subprogramas de
entrada y salida, por ejemplo podemos usar el registro 'ax'.
Mientras nosotros no cambiemos el funcionamiento del MS-Dos, podemos hacer esto para modificar nuestros
subprogramas. Usaremos entonces 'al' para contener el carácter mostrado por putc y ax para contener la dirección del string
a ser mostrado por puts.
El subprograma getc retorna el carácter ingresado en 'al', entonces este no debe ser cambiado.
Ejemplo 3.21: Para ilustrar el uso de la nueva definicion de putc y puts, reescribiremos el programa 3.19, el cual convertía
una letra mayúscula a su equivalente minúscula:
.code
; main program
start:
mov ax, @data
mov ds, ax
mov ax, offset msg1
call puts
call getc ; read uppercase letter
mov bl, al ; save character in bl
add bl, 32d ; convert to lowercase
mov ax, offset result
call puts ; display result message
mov al, bl
call putc ; display lowercase letter
mov ax, 4c00h
int 21h ; return to ms-dos
Guardando registros
Existe una desventaja en utilizar el metodo descripto anteriormente.
Ahora nosotros utilizamos dos registros en lugar de uno para almacenar la infomación deseada. Esto reduce el número de
registros disponibles para almacenamiento.
Otro punto importante, es en el subprograma puts, por ejemplo, el registro dx es modificado.
Si nosotros usamos este registro en un programa antes de la llamada a puts, entonces la información almacenada en dx se
perderá, a menos que la guardemos.
Esto puede causar sutiles pero serios errores en los programas que son difíciles de detectar. El siguiente fragmento de
código ilustra el problema:
mov dx, 12 ; dx = 12
mov ax, offset msg1 ; display message msg1
call puts ; dx gets modified
add dx, 2 ; dx will NOT contain 14
Este error se manifiesta mucho despues, en la ejecución del programa. Los principiantes tienen frecuentemente
este tipo de error en lenguaje ensamblador.
Cuando un programa se comporta extrañamente, es usualmente una buena idea utilizar técnicas de depuración para
chequear este tipo de situaciones, por ejemplo chequear que los subprogramas no modifiquen registros que son utilizados
para otros propósitos.
Esto es generalmente un problema con todos lo subprogramas que cambian el valor de registros. Entonces, si
nosotros estamos usando el registro 'ah' antes de llamar a un subprograma, debemos guardar su valor antes de ejecutar la
llamada.
Incluso, el subprograma del MS-Dos invocando al int pueden cambiar el valor de los registros. Por ejemplo el
subprograma número 2h (usado por getc) hace eso. Este modifica el registro 'al' para retornar el valor ingresado por el
teclado. Los subprogramas del MS-Dos tambien pueden cambiar otros valores de registros y debemos ser muy cuidadosos
de chequear por esto cuando usamos tales subprogramas.
Existe una solucion sencilla a este problema. Nosotros podemos y debemos escribir nuestros subprogramas de
manera que antes de modificar cualquier registro este primero guarde el valor de ese registro. Entonces, antes de retornar de
un subprograma, restauramos el registro a su valor original.
( En el cado de getc, sin embargo, nosotros no guardamos el valor del registro 'al', porque queremos que getc lo lea.)
El stack es típicamente usado para guardar y recuperar los valores de los registros usados en subprogramas.
El stack es una area de memoria (Ram) donde podemos almacenar temporariamente ítems. Solemos decir que
hacemos push dentro del stack para guardar el dato. También solemos decir que hacemos pop del stack para obtener un
dato.
El 8086 provee instrucciónes push y pop para almacenar y recuperar itens del stack. Ver capítulo 2 para más
detalles.
Ejemplo 3.22 : Nosotros reescribiremos los subprogramas getc, putc, y puts para guardar los valores de los registros y
restaurarlos apropiadamente. La siguiente versión de getc, putc y puts son por lo tanto seguras en el sentido que los registros
no cambian de valor sin que el programador no realice el cambio.
Note que nosotros sacamos valores del stack en el orden inverso en que fue colocado debido a la naturaleza LIFO
del stack (Last Input First Output).
De ahora en adelante, cuando nos refiramos a getc, putc, y puts en este documento, las definiciones anteriores se
daran como entendidas.
OBS: Es vital, que cuando usemos el stack en subprogramas, saquemos todos los itens puestos en el stack por el
subprograma, antes de retornar del mismo.
Fallas de este tipo, dejando un item en el stack, será utilizado por la instrucción ret como dirección de retorno. Esto
causará que nuestro programa se comporte inadecuadamente. Si tenemos suerte fallará. En otro caso, este continuará su
ejecución desde cualquier punto en el programa, produciendo resultados incomprensibles.
Un punto que vale la pena repetir: cuando usamos el stack en un subprograma, debemos asegurarnos de remover
todos los items colocados en el, antes de retornar del subprograma.
Ejemplo 3.23: En este ejemplo ilustramos el uso de la instrucción jmp para implementar un bucle sin fin (no es algo que
normalmente haríamos).
again:
call getc ; read a character
call putc ; display character
jmp again ; jump to again
Este es un ejemplo de saldo de retroceso, el control del programa es transferido de un lugar a otro.
El fragmento de codigo causa repetidas instrucciones entre la instrucción jmp y su etiqueta.
Puedes colocar etiquetas en cualquier punto del programa y la etiqueta puede estar en la misma línea que la
instrucción, por ejemplo:
el programa anterior ejecutará para siempre un bucle y deberás pararlo con una interrupción,
por ejemplo precionando ctrl/c o desconectando la máquina.
Ejemplo 3.24: El siguiente fragmento de código ilustra un forward jump, el control es transferido a una posición posterior
del programa:
finish:
mov ax, 4c00h
int 21h
En este caso el código entre la instrucción y la etiqueta finish nunca será ejecutado porque el jmp causa que salte sobre el.
La instrucción de jump condicional son algunas veces llamadas instrucciones con condiciones. Estas testean el
valor de los flags en el registro.
(El valor del registro 'cx' es usado en algunos de ellos). Una de las instrucciones condicionales de jump es jz la cual salta a
otra ubicación del programa igual que la instrucción jmp, excepto que lo hace si el z-flag es 1, por ejemplo si el resultado de
la última instrucción fue 0. (La instrucción jz puede ser entendida como “jump con condición cero ” o “jump en cero”).
mov ax, 2 ; ax = 2
sub ax, bx ; ax = 2 - bx
jz nextl ; jump if (ax-bx) == 0
inc ax ; ax = ax + 1
nextl:
inc bx
Lo anterior es equivalente a:
ax = 2;
if ( ax != bx )
{
ax = ax + 1 ;
}
bx = bx + 1 ;
En este ejemplo, el z-flag estara seteado (a 1) unicamente si bx contiene 2. Si esto ocurre, entonces la instrucción jz
causará el salto.
El 8086 provee de la instrucción cmp para comparar. Esta trabaja exactamente como la instrucción sub excepto que
el operador no es afectado, por ejemplo, este substrae el codigo del destino, pero descarta el resultado dejando el operador
de destino sin cambios. Sin embargo, este modifica el stado del registro. Todos los flags que son seteados o reseteados por
sub son seteado o reseteados por cmp. De esta manera, si tu quieres comparar dos valores, es mas razonable usar la
instrucción cmp.
Nota: El cmp compara el operando de destino con el operando del codigo. El orden es obviamente importante porque por
ejemplo, una instrucción como jng dest, source causara bifurcación únicamente si dest <= source.
Algunas instrucciones condicionales pueden tener más de un nombre, por ejemplo jz (Jump on zero) es tambien llamada je
(jump on equal). De esta manera el codigo anterior puede ser escritor como :
cmp ax, bx
je equals ; jump if ax == bx
Este nombre para la instrucción hace el código mas entendible en situaciones donde estamos interesado en la
igualdad de dos valores.
Los jumps con condiciones pueden ser usado para saltos hacia adelante (como en los ejemplos anteriores) o hacia
atrás y en este caso implementar loops.
Existen 16 jumps con condicionales los cuales testean cuales flags o combinación de flags estan seteado o fueron
limpiados.
Sin embargo, más que concentrarse en el seteo de flags, es mas fácil entender la comparación de números
(signados y sin signo separadamente) como igual, no igual, menor que, mayor que , mayor o igual, o menor o igual que.
Tabla 3.1 lista las instrucciones con condiciones. Estas tienen nombres alternativos además.
je / jz equal/zero zf = 1
jo overflow of = 1
js sign sf = 1
jns no sign sf = 0
Notas:
cf,of,zf,pg, y sf son flags de estado de carry,overflow, cero, paridad, y signo.
En las instrucciones anteriores, la letra “a” puede ser tomada como “sobre”(above), y la letra “b” significa
“abajo”(below). Las instrucciones que usan esas letras (por ej. ja, jb, etc) operan con números sin signo.
La letra “g” puede ser tomada como “mayor que” (greater tan) y la letra “l” puede ser utilizada como “menor que”
(less than). Instrucciones que usan esas letras (por ej. jg, jl, etc.) operan sobre números con signos.
Es responsabilidad del programador usar la instrucción correcta dependiendo si va a manipular numeros signados o
no.
Existen tambien cuatro instrucciones envolviendo el registro 'cx': jcxz, loop, loope, loopne. Por ejemplo, la
instrucción jcxz causa un jump si el contenido del registro cx es cero.
Esto consiste de una condición para ser evaluada y una acción a ser tomada si la condición es verdadera.
Ejemplo 3.27:
C versión:
if ( i == 10 )
{
i=i+5;
j=j+5;
}
/* Rest of program */
Existen dos formas de escribir esto en Assembler. Un método es testear si la condición (i == 10) es verdadera. Esto
bifurca la salida del carry out si la condición es verdadera. Si la condición es falsa, existe una segunda salida del programa.
8086 version 1:
cmp i, 10
je label1 ; if i == 10 goto label1
jmp rest ; otherwise goto rest
label1: add i, 5
add j, 5
rest: ; rest of program
El segundo metodo es testear si la condición (i != 10) es verdadera, bifurcando el carry out al resto del programa en
este caso. Si este no es el caso, entonces las instrucciones son ejecutadas:
8086 versión 2:
cmp i, 10
jne rest ; if i != 10 goto rest
add i, 5 ; otherwise do action part
add j, 5
rest: ; rest of program
De esta manera, en general, para implementar un if-then en el lenguaje Assembler, nosotros debemos testear la
inversa de la condición que deseamos usar en el lenguaje de alto nivel, asi como se demostro en la versión 2 anteriormente.
if ( condition )
{
/* action1 statements */
}
else
{
/* action2 statements */
}
Ejemplo 3.28: Escribe un fragmento de codigo para leer un carácter ingresado por el usuario y compara este al carácter 'A'.
Muestra un mensaje apropiado si el usuario ingresa una 'A'. Este fragmento de código es básico para un juego de
adivinanzas.
Version en C:
Versión 8086:
end_else:
Si el valor leído es la letra 'A', entonces el jne no sera ejecutado, yes_msg sera mostrado y el control se transferirá
a end_else. Si el valor ingresado no es 'A', entonces el jne es ejecutado y el control es transferido a is_not_an_A.
Ejemplo 3.29: el código completo para jugar un juego de adivinanzas basado en el fragmento de código anterior es:
.data
prompt db “Guessing game: Enter a letter (A to Z): $“
yes_msg db CR, LF,“You guessed correctly !! $“
no_msg db CR, LF,“Sorry incorrect guess $“
.code
start:
mov ax, @data
mov ds, ax
mov ax, offset prompt
call puts ; prompt for input
call getc ; read character
cmp al, ‘A’
jne is_not_an_a ; if (al != ‘A’) skip action
mov ax, offset yes_msg ; if action
call puts ; display correct guess
jmp end_else1 ; skip else action
is_not_an_A: ; else action
mov ax, offset no_msg
call puts ; display wrong guess
end_else1:
Nota: En este programa nosotros usamos la etiqueta end_else1 para indicar el fin de la estructura if-then-else.
Esto es importante, si usas esta estructura un número de veces en el programa, emplear diferentes etiquetas cada
vez que la estructura es usada. De esta manera una etiqueta como end_else2 puede ser usada para la segunda ocurrencia de
la contrucción aunque para esto es preferido algo mas significativo como is_not_an_A.
Ejemplo 3.30: Modificar el programa 3.19, el cual convierte una letra mayúscula a minúscula, para testear la letra ingresada.
Testear si la letra es mayúscula, nosotros necesitamos testear si su código ASCII esta en el rango entre 65 a 90 ('A' a Z'').
En C sería:
Lo contrario sería:
La variable c contiene el código ASCII del carácter ingresado. Este es inicialmente comparado con el código ASCII de 'A' y
'Z'.
La notacion && usada en la primera condición, entiéndase como AND, si el valor de c es mayor o igual que 'A' y es menor
o igual que 'Z', entonces c contiene una letra mayúscula.
La notación | | usada en la segunda condición, entiéndase como OR, en otras palabras, si el valor de c es menor que
'A' o si éste es mayor que 'Z', no es una letra mayúscula. Nosotros usamos la primera condición en el programa para 8086
abajo.
Versión C:
.data
msg1 db CR, LF,‘Enter an uppercase letter: $’
result db CR, LF,‘The lowercase equivalent is: $’
bad_msg db CR, LF,‘Not an uppercase letter: $’
start:
mov ax, @data
mov ds, ax
mov ax, offset msg1
call puts
call getc ; read uppercase letter
mov bl, al ; save character in bl
cmp bl, ‘A‘
jl invalid ; if bl < ‘A‘ goto invalid
cmp bl, ‘Z‘ ; if bl > ‘Z‘ goto invalid
jg invalid
; otherwise its valid
add bl, 32d ; convert to lowercase
mov ax, offset result
call puts ; display result message
mov al, bl
call putc ; display lowercase letter
jmp finish
invalid:
mov ax, offset bad_msg ; not uppercase
call puts ; display bad_msg
mov al, bl
call putc ; display character entered
finish:
mov ax, 4c00h
int 21h ; return to ms-dos
; subprograms getc, putc and puts should be defined here
end start
Ejercicios:
3.13Escribe un programa para leer un dígito y mostrar un mensaje de error si no es un dígito el ingresado
3.14 En el fragmento de código a continuación donde será ejecutado desde <jump-on-condition> es reemplazado por (a) je
lab1; (b) jg lab1; (c ) jle lab1; (d) jz lab1
3.15Escribe un programa para testear un carácter ingresado por teclado y que transfiera el control a la etiqueta ok_here, si
el carácter es:
Bucles (Loops):
Nosotros ya hemos visto como podemos implementar bucles utilizando la instrucción jmp para hacer jumps hacia
atrás en un programa. Sin embargo, notamos que jmp es un jump incondicional, se pueden generar loops infinitos. La
solución a esto es usar jumps condicionales. Por ejemplo, un while loop para mostrar por pantalla el carácter '*' 60 veces
puede ser implementado como en el ejemplo 3.31.
version en C:
count = 1 ;
while ( count <= 60 )
{
putchar(‘*’) ;
count = count + 1 ;
}
versión 8086 :
mov cx, 1d ; cx = 1
mov al, ‘*’ ; al = ‘*’
disp_char:
cmp cx, 60d
jnle end_disp ; if cx > 60 goto end_disp
call putc ; display ‘*’
inc cx ; cx = cx + 1
jmp disp_char ; repeat loop test
end_disp:
La instrucción jnle (jump si no es menor o igual) puede ser escrita tambien como jg (jump si es mayor que). Nosotros
usamos una técnica similar en la implementación de la estructura if – then en la que testeamos la inversa de la condición
usada en el fragmento de código C (count <= 60). Esto nos permite a nosotros escribir código en Assembler más
entendible.
Ejemplo 3.32: Escriba un fragmento de código que muestre el carácter de la 'a' a la 'z' en la pantalla usando el conocimiento
que el código ASCII tiene una secuencia específica. Esto significa que el código para 'b' es más grande que que el código
para 'a' y el código para 'c' es más grande que el código para 'b' y asi sucecisavamente.
Versión C
Versión 8086 :
abcdefghijklmnopqrstuvwxyz
En los próximos dos ejemplos, nosotros especificaremos cuantas veces el loop hará iteraciones.
Frecuentemente encontramos casos donde nosotros no conocemos cuantas veces el bucle será ejecutado. Por ejemplo,
en cada iteración preguntamos al usuario si el bucle debe ser repetido y continuamos o no en base a la respuesta del usuario.
Ejemplo 3.33: El programa 3.19 lee una letra mayúscula, la convierte a minúscula y muestra a pantalla. Nosotros
modificaremos esto, para que el usuario pueda repetir este proceso tantas veces como desee. Al usuario le preguntamos si
quiere continuar con la tecla 'y' despues de cada iteración.
Versión C:
main()
{
char c, reply;
reply = ‘y‘;
while ( reply == ‘y‘ )
{
printf(“\nEnter an uppercase letter: “);
c = getchar();
c = c + ( ‘a’ - ‘A’ ) ; /* convert to lowercase */
printf(“\nThe lowercase equivalent is: %c “, c);
printf(“\nEnter y to continue: “);
reply = getchar();
}
}
Versión 8086:
.data
reply db ‘y’
msg0 db CR, LF, ‘Enter y to continue: $’
msg1 db CR, LF, ‘Enter an uppercase letter: $’
result db CR, LF, ‘The lowercase equivalent is: $’
.code
; main program
start:
mov ax, @data
mov ds, ax
readloop:
cmp reply, ‘y’ ; while (reply == ‘y‘)
jne finish ; do loop body
mov ax, offset msg1
call puts ; prompt for letter
call getc ; read character
mov bl, al ; save character in bl
add bl, 32d ; convert to lowercase
mov ax, offset result
call puts ; display result message
mov al, bl
call putc ; display lowercase letter
mov ax, offset msg0
call puts ; prompt to continue
call getc ; read reply
mov reply, al ; save character in reply
jmp readloop ; repeat loop test
finish:
mov ax, 4c00h
int 21h ; return to ms-dos
; user defined subprograms should be defined here
end start
Ejecutando este programa obtenemos como resultado, asumiendo que el usuario ingresa el carater C, X y n:
Ejercicios:
3.16 Modifique el programa del ejemplo 3.33 para testear que la letra ingresada es una letra minúscula válida. Si no lo es
muestre un mensaje de error y el programa debe continuar ejecutando tanto tiempo como el usuario desee.
3.17 Modifique el juego de adivinanzas (Programa 3.29) para que permita al usuario ingresar tres adivinanzas, terminando
si ninguna es correcta.
3.18 Modifique el juego de adivinanzas para que permita al usuario ingresar las adivinanzas cuantas veces desee,
terminando si ninguna es correcta.
3.19 Modifique el juego de adivinanzas para que el bucle itere hasta que la respuesta correcta sea ingresada.
Para usar la instrucción loop, simplemente almacene el número de iteraciones requeridas en el registro cx y
contruya un cuerpo para el loop. La última instrucción del cuerpo del loop es la instrucción loop.
Nota 1: El cuerpo del loop siempre será ejecutado por última vez, desde la instrucción loop testea el valor del registro cx
despues de ejecutar el cuerpo del loop.
Nota 2: ¿Qué pasa si cx es inicializado a cero? La instrucción loop decrementa cx testeando antes la condición (cx != 0).
De esta manera nosotros continuamos iterando el loop, con cx comenzando por los negativos. Esto se repetirá 65.536 veces.
Porqué? La razón es por que nosotros substraemos 1 del registro cx hasta que encontremos 0. Eventualmente, al hacer cx
mas negativo, el mayor número negativo que cx puede contener será alcanzado. Cx es un registrador de 16 bits, y nosotros
sabemos del apendice 2, que este número es -32768d, lo cual representa en un numero de 16 bits 1000 0000 0000 0000.
Sustrayendo 1 a este campo de 16 bits obtenemos 0111 1111 1111 1111 o 32767d.
Nosotros podemos subtraer 1 de este número 32767 veces antes de alcanzar 0, con lo cual termina la instrucción
loop. De esta manera el total de veces que itera es 32768 + 32767 + 1 equivalente a 65535 + ( el 1 extra es porque cx
empieza de cero y es decrementado en 1 depues del test).