Apuntadores contra Arreglos y manejo del programa SPIM

Anuncio
2.10 Apuntadores contra arreglos.
El manejo de apuntadores es uno de los aspectos más interesantes en los lenguajes de
programación; los apuntadores y los arreglos comparten algunas características, sin
embargo, muchas veces los compiladores trasladan de diferente manera a un programa con
arreglos que otro con apuntadores.
Consideraremos dos procedimientos, uno con arreglos y el otro con apuntadores, en ambos
casos el objetivo será el mismo: Limpiar el arreglo, es decir, colocar 0 en todas sus
localidades.
void
{
clear1( int array[], int size )
/* Versión con arreglos */
int i;
for( i = 0; i < size; i++)
array[i] = 0;
}
void
{
clear2( int *array, int size )
/* Versión con apuntadores */
int *p;
for( p =&array[0];p < &array[size]; p++)
*p = 0;
}
Versión con arreglos de Clear
Los dos argumentos se reciben en los registros $a0 y $a1, respectivamente. La variable i se
asocia con $t0 puesto que clear no llama a otros procedimientos.
clear1:
add
$t0, $zero, $zero
# se inicializa i = 0
for1:
slt
beq
add
add
add
sw
addi
j
$t1, $t0, $a1
$t1, $zero, fin_for1
$t2, $t0, $t0
$t2, $t2, $t2
$t2, $t2, $a0
$zero, 0($t2)
$t0, $t0, 1
for1
# $t1 = 1 si i < size
# Si $t1 tiene 0, el for termina
# $t2 = 2*i
# $t2 = 4*i
# $t2 tiene la dirección de array[i]
# Limpia la localidad array[i]
# i ++
fin_for1:
jr
$ra
Versión con apuntadores de Clear
Se tiene la misma asociación de registros con los parámetros y el apuntador p se asocia con
el registro $t0
clear2:
add
$t0, $a0, $zero
# p apunta al comienzo del arreglo
for2:
add
add
add
slt
beq
sw
addi
j
$t1, $a1, $a1
$t1, $t1, $t1
$t1, $t1, $a0
$t2, $t0, $t1
$t2, $zero, fin_for2
$zero, 0($t0)
$t0, $t0, 4
for2
# $t1 = 2*size
# $t1 = 2*t1 = 4*size
# $t1 = &array[size]
# $t2 = 1 si p < &array[size]
# Si $t2 tiene 0, el for termina
# Limpia la localidad apuntada por p
# p ++, el apuntador avanza 4
fin_for2:
jr
$ra
En el código anterior se muestra una versión del código MIPS basada en apuntadores,
puede observarse que, debido a que la dirección del elemento array[size] no cambia, las tres
instrucciones empleadas para calcularlo pueden omitirse en el lazo, produciendo el código:
clear2:
for2:
fin_for2:
add
$t0, $a0, $zero
# p apunta al comienzo del arreglo
add
add
add
slt
beq
sw
addi
j
$t1, $a1, $a1
$t1, $t1, $t1
$t1, $t1, $a0
$t2, $t0, $t1
$t2, $zero, fin_for2
$zero, 0($t0)
$t0, $t0, 4
for2
# $t1 = 2*size
# $t1 = 2*t1 = 4*size
# $t1 = &array[size]
# $t2 = 1 si p < &array[size]
# Si $t2 tiene 0, el for termina
# Limpia la localidad apuntada por p
# p ++, el apuntador avanza 4
jr
$ra
Este código es mas rápido por que incluye menos instrucciones en la parte
repetitiva. En general, los compiladores capaces de optimizar código intentan manejar los
arreglos sumando una variable para obtener la dirección del i-ésimo elemento, en lugar de
realizar multiplicaciones. El código resultante será más rápido por que para toda
arquitectura, una suma es más rápida que una multiplicación.
Si la función clear se aplica sobre un arreglo de 500 elementos, ¿Qué tan rápida es la
versión 2 del código con respecto a la versión 1(Suponiendo que se realiza una instrucción
en cada ciclo de reloj)?
La respuesta a esta pregunta puede obtenerse recordando los aspectos vistos acerca del
rendimiento de las computadoras (se deja como ejercicio).
2.11 Un simulador del repertorio de instrucciones
En esta sección se describe al simulador SPIM, creado por el Dr. James Larus, graduado en
la Universidad de Wisconsin, Madison. Y actualmente investigador de la empresa
Microsoft.
SPIM es un simulador autónomo para programas en lenguaje ensamblador escritos para los
procesadores R2000/R3000, los cuales son procesadores de 32 bits de la corporación MIPS.
SPIM lee y ejecuta el código en lenguaje ensamblador, proporciona un depurador simple y
un juego simple de servicios del sistema operativo.
SPIM soporta casi el conjunto completo de instrucciones del ensamblador-extendido para
el R2000/R3000 (omite algunas comparaciones de punto flotante complejas y detalles del
sistema de paginación de memoria.).
El doctor Larus tiene disponible al programa SPIM para diferentes sistemas operativos, los
cuales pueden obtenerse libremente desde su página web situada en:
http://www.cs.wisc.edu/~larus/
en la que inmediatamente se encuentra el vínculo al programa SPIM.
encuentra el código fuente completo y documentación.
También se
Es necesario descargar el programa para evaluar al repertorio de instrucciones bajo estudio.
En esta sección solo se muestran algunos aspectos del programa SPIM útiles para simular
los programas hasta el momento realizados.
En la versión para WINDOWS el programa SPIM tiene el aspecto que se presenta en la
figura 2.7, en la que se distinguen cuatro ventanas:
ƒ
La primer ventana contiene a los registros, se muestra el valor de todos los registros de
propósito general (de $0 a $31), además del Contador del Programa (PC) y de otros
registros para el manejo de excepciones (una excepción es un evento erróneo debido a
alguna incongruencia durante la ejecución de un programa). También se muestran dos
registros HI y LO, estos registros son dedicados a las multiplicaciones y divisiones
ƒ
La segunda ventana contiene una parte de memoria en la que se colocará el código de
usuario (el código a evaluar), el código se muestra de manera simbólica (ensamblador).
En esta ventana se observa al de código descrito en el archivo exceptions.s, este código
corresponde a una especie del kernel de la máquina. Este código incluye una llamada a
la función main, de manera que cualquier programa que se quiera simular deberá incluir
al procedimiento principal (main). La idea es que los usuarios avanzados puedan hacer
su propio archivo para que hagan un manejo diferente de las excepciones.
ƒ
La tercer ventana muestra una parte de la memoria en la que se colocarán los datos, en
hexadecimal. Esto incluye una sección de propósito general, una parte dedicada a la pila
(stack) y otra que forma parte del Kernel.
ƒ
La cuarta ventana es la ventana de mensajes, en la que se describen los diferentes
eventos que van ocurriendo durante la simulación.
Fig. 2.7 Aspecto del programa SPIM para Windows
2.11.1 La consola del programa SPIM
Además de las cuatro ventanas que se encuentran en la ventana principal del programa,
cuando el programa se ejecuta se despliega en pantalla otra ventana conocida como la
consola del programa SPIM (ver figura 2.8).
La consola es el mecanismo por medio del cual se van a insertar datos al programa o se van
a observar algunos resultados del mismo. El Kernel incluido permite el manejo de una
instrucción denominada SYSCALL. Con SYSCALL se realiza una llamada al Kernel para
solicitar algún servicio, que puede consistir en la captura de un dato o bien la presentación
de resultados en la consola.
Antes de invocar a SYSCALL, se debe especificar el número de servicio en el registro $V0,
y si el servicio requiere argumentos, éstos se deberán colocar en los registros $a0 y $a1,
dependiendo del número de argumentos, sin embargo, si el servicio es para números en
punto flotante, se utilizará al registro $f0 para el argumento (La arquitectura MIPS incluye
32 registros para el manejo de números en punto flotante, y un hardware dedicado para las
operaciones en punto flotante). En la tabla 2.2 se muestran todos los servicios que soporta
el Kernel.
Fig. 2.8 La consola del Programa SPIM
Tabla 2.2 Servicios que nos proporciona el Kernel del Programa SPIM
2.11.2 Pseudo instrucciones.
Debido a que el repertorio MIPS es un repertorio de instrucciones reducido, para dar un
poco mas de flexibilidad a los programadores, es posible generar un conjunto de pseudo
instrucciones; una pseudo instrucción realiza algún tipo de operación, sin embargo no tiene
una interpretación directa en Hardware, sino que tiene que traducirse a una o mas
instrucciones reales para que pueda ser ejecutada.
Así por ejemplo, la pseudo instrucción:
move reg_destino, reg_fuente
Mueve el registro fuente al registro destino, pero no es una instrucción real, sino que el
simulador SPIM la traduce a:
ori
reg_destino, $zero, reg_fuente
Para las multiplicaciones, se puede utilizar la pseudo instrucción:
mul
$s1, $s2, $s3
esta pseudo instrucción en realidad es traducida en las instrucciones siguientes:
mult $s2, $s3
mflo $s1
# Esta es la instrucción que multiplica a $s2 con $s3, pero el
# resultado lo deja en los registros HI y LO
# Esta instrucción coloca la parte baja del resultado y la
# coloca en $s1toma la parte baja del resultado
Puede notarse que la pseudo instrucción es suficiente cuando se sabe que el resultado
alcanza en un registro de 32 bits. Pero si se están manipulando números muy grandes,
además de la pesudo instrucción se debería usar a la instrucción:
mfhi
$s4
# Para colocar la parte alta en un registro de propósito general
La pseudo instrucción mul también puede usarse con el segundo parámetro con un valor
inmediato, por ejemplo la pseudo instrucción:
mul $t4, $t1, 4
Es traducida a:
ori $1, $0, 4
mult $9, $1
mflo $12
Otra pseudo instrucción bastante útil es la siguiente:
la
$a0, str1
Cuando se codifica un programa y se van a utilizar cadenas constantes, se sabe que éstas se
colocarán en memoria, sin embargo se ignora en que dirección serán colocadas, por lo que
no se sabría como direccionarlas. Con la se obtiene en el registro $a0 la dirección donde
inicia la cadena str1 (la – load address). Esta pseudo instrucción es traducida a dos
instrucciones, la primera para cargar la parte alta de la dirección (lui) y la segunda para
obtener la parte baja (ori).
Existen más pesudo instrucciones, sólo se han mencionado las mas comunes y que son
necesarias para el desarrollo de algunos programas que se realizarán para la evaluación del
simulador. En el apéndice A del texto “Computer Organization & Design, The
hardware/software interface” (página A-51) se encuentra un listado con todas las
instrucciones de los procesadores MIPS R2000/R3000 y las pseudo instrucciones que
soporta el simulador SPIM. Este apéndice esta disponible en formato PDF en la página del
Dr. Larus, su referencia es: http://www.cs.wisc.edu/~larus/HP_AppA.pdf .
2.11.3 Ejemplos de uso del simulador:
Ejemplo 1: El programa “Hola Mundo”
Se trata de un programa que desplegará en la consola a la cadena “HOLA MUNDO”,
básicamente se requiere obtener la dirección del inicio de la cadena y solicitar el servicio 4
al kernel (con la instrucción SYSCALL).
El código del programa es:
main: addi $v0, $zero, 4
la
$a0, cadena
syscall
jr
$31
.data
cadena: .asciiz
# Se usará el servicio 4
# Se obtiene el argumento
# Solicita el servicio
# Termina la función principal
"Hola Mundo"
La salida en la consola es:
Observaciones:
ƒ El código se puede escribir con cualquier editor de texto (texto sin formato) y salvarse
con cualquier extensión, se sugiere s por asociación con el programa.
ƒ El código principal debe incluir a la etiqueta main por que en el kernel del programa
existe un salto hacia esa etiqueta.
ƒ La ejecución puede hacerse paso a paso con <F10> o con múltiples pasos con <F11>. O
simplemente con el comando go, con <F5>.
ƒ Una vez que finalice el código de usuario, si se continúa ejecutando, se regresa del main
al Kernel y solicita el servicio 10 (exit).
Ejemplo 2: Un programa que suma dos números.
En este ejemplo se usará a la consola para obtener dos enteros, luego los enteros se sumarán
y se mostrará el resultado.
El código del programa:
main: addi $v0, $zero, 4
la
$a0, str1
syscall
str1:
str2:
str3:
str4:
# Servicio 4
# se imprime una cadena
# para pedir un número
addi $v0, $0, 5
syscall
add
$8, $0, $v0
# Servicio 5
# se lee el número
# se coloca en $8
addi $v0, $zero, 4
la
$a0, str2
syscall
# Servicio 4
# se imprime una cadena
# para pedir el otro número
addi $v0, $0, 5
syscall
add
$9, $0, $v0
# servicio 5
# se lee el otro numero
# se coloca en $9
addi $v0, $zero, 4
la
$a0, str3
syscall
# Servicio 4
# para indicar que se
# dará el resultado
add
$a0, $8, $9
addi $v0, $0, 1
syscall
# Se coloca la suma como argumento
# Servicio 1
# se muestra el resultado
addi $v0, $zero, 4
la
$a0, str4
syscall
# Servicio 4
# muestra una cadena de
# terminación del programa
jr
# fin del main
$31
.data
.asciiz
"Dame un numero: "
.asciiz
"Dame otro numero: "
.asciiz
"La suma de los numeros es : "
.asciiz
"\n\nFin del programa, Adios . . ."
Una corrida generó en la consola:
Ejemplo 3: El factorial de un número.
Este programa está basado en la función recursiva que se presentó en la sección 2.5
(Soporte de procedimientos) sólo se le hicieron algunas modificaciones para el manejo
correcto de las constantes.
En este programa se utilizó la pseudo instrucción li (por load immediate) para cargar una
constante en un registro, que es equivalente a hacer una operación OR del registro 0 con la
constante y colocar el resultado en el registro que se quiere cargar.
El código del programa:
main:
addi
sw
$sp, $sp, -4
$ra, 4 ($sp)
li
$v0,
la
$a0,
syscall
li
$v0,
syscall
add
$s0,
4
str1
# Salida a la consola
5
# Lectura de un numero
$0, $v0
# El numero esta en $v0, se copia a $s0
add
jal
$a0, $0, $s0
fact
# Prepara el parametro
# Llama al factorial
add
$s1, $v0, $zero
# Respalda el resultado
li
$v0,
la
$a0,
syscall
addi $v0,
add
$a0,
syscall
li
$v0,
la
$a0,
syscall
addi $v0,
add
$a0,
syscall
li
$v0,
la
$a0,
syscall
fact:
# Hace espacio en la Pila
# Salva la dirección de retorno
4
str2
# Una cadena a la consola
$0, 1
$s0, $zero
# Una entero a la consola
4
str3
# Una cadena a la consola
$0, 1
$s1, $zero
# Una entero a la consola
4
str4
lw
addi
jr
$ra, 4 ($sp)
$sp, $sp, 4
$31
addi
sw
sw
$sp, $sp, -8
$ra, 4 ($sp)
$a0, 0 ($sp)
# Recupera la dirección de retorno
# Restablece el tope de la Pila
#
#
#
#
La funcion que obtiene el factorial
Hace espacio en la Pila
Salva la dirección de retorno
Salva al argumento n
# Se evalúa para ver si ocurre el caso base (cuando n < 1):
slti $t0, $a0, 1
# $t0 = 1 si n < 1
beq
$t0, $zero, L1
# salta a L1 si no ocurre el caso base
# Si ocurre el caso base, deberían recuperarse los datos de pila,
# pero como no se han modificado, no es necesario.
# Lo que si se requiere es restablecer al puntero de la pila.
addi
addi
jr
$v0, $zero, 1
$sp, $sp, 8
$ra
# retorno = 1
# Restablece al apuntador de la pila
# Finaliza regresando el resultado en $v0
# Si no ocurre el caso base, prepara la llamada recursiva
L1:
addi $a0, $a0, -1
# n = n - 1
jal
fact
# llama a fact con n - 1
# Después de la llamada, se hace la restauración de los registros:
lw
$a0, 0($sp)
# Recupera el valor de n
lw
$ra, 4($sp)
# recupera la dirección de retorno
addi $sp, $sp, 8
# Restablece al apuntador de la pila
#Para concluir, se actualiza el valor de retorno y se regresa el control
al invocador:
mul
$v0, $a0, $v0
# Retorno = n * fact (n - 1)
jr
$ra
# regresa al invocador
str1:
str2:
str3:
str4:
.data
.asciiz
.asciiz
.asciiz
.asciiz
"Dame un numero: "
"El factorial del numero "
" es : "
"\n\nFin del programa, Adios . . ."
Como ejemplo, se obtuvo el factorial de 7.
Ejemplo 4: Manejo de un arreglo.
En este ejemplo se pretende mostrar como un arreglo de n enteros puede ser ubicado en la
pila. Para ello solo se piden algunos enteros al usuario para luego imprimirse en pantalla en
orden inverso.
El código C que realiza las actividades deseadas es:
void main()
{
int i, n, *A, *t;
printf("Manejo de un arreglo\n");
printf("Indica el tamaño del arreglo: ");
scanf("%d", &n);
A = (int *) malloc(n*sizeof(int));
for( t = A, i = 0; i < n; i++, t++) {
printf(" Dame el dato %d : ", i);
scanf("%d", t);
}
printf("\nLos números en orden inverso: \n");
for( t = &A[n-1], i = 0; i < n; i++, t--)
printf(" %d\n", *t);
}
free(A);
Las variables i, n, A y t se asocian con los registros: $t0, $t1, $t2 y $t3 (este procedimiento
es aislado).
El código MIPS correspondiente al código C anterior es:
# Programa que maneja un arreglo imprimiendo los elementos en orden inverso
main:
# El Primer Mensaje
li $v0, 4
la $a0, str1
syscall
#Mensaje que pide el tamaño del arreglo
li $v0, 4
la $a0, str2
syscall
#Se toma el valor
li $v0, 5
syscall
add $t1, $v0, $0
#El tamaño del arreglo se guardo en $t1
# Se reserva el espacio en memoria
mul $t4, $t1, 4
sub $sp, $sp, $t4
addi $t2, $29, 0
#en $t4 esta el total de bytes requeridos en la pila
#Se hace espacio en la pila para los datos
#$t2 es el apuntador al comienzo del arreglo
#inicia el primer for:
addi $t0, $0, 0
addi $t3, $t2, 0
for1:
# i = 0
# t = A
slt $t5, $t0, $t1
beq $t5, $zero, fin_for1
#Se pide el número
li $v0, 4
la $a0, str3
syscall
#imprime el indice del numero
li $v0, 1
add $a0, $0, $t0
syscall
#imprime los dos puntos
li $v0, 4
la $a0, str4
syscall
#Toma el valor de la consola
li $v0, 5
syscall
sw $v0, 0($t3)
addi $t0, $t0, 1
addi $t3, $t3, 4
#i ++
#t ++
j for1
#En este ciclo se pidieron los Numeros
fin_for1:
#El mensaje de aviso
li $v0, 4
la $a0, str5
syscall
# Inicio del segundo for
addi $t0, $0, 0
# i = 0
addi $t4, $t1, -1
# $t4 = n - 1
mul $t4, $t4, 4
# $t4 = 4 * (n - 1)
add $t3, $t2, $t4
# t = &A[n-1]
for2:
slt $t5, $t0, $t1
beq $t5, $zero, fin_for2
#imprime el numero
lw $t6, 0($t3)
li $v0, 1
add $a0, $0, $t6
syscall
# Primero lo carga
#imprime el retorno de carro
li $v0, 4
la $a0, str6
syscall
addi $t0, $t0, 1
addi $t3, $t3, -4
#i ++
#t --
j for2
fin_for2:
mul $t4, $t1, 4
add $sp, $sp, $t4
jr $ra
str1:
str2:
str3:
str4:
str5:
str6:
#en $t4 esta el total de bytes requeridos en la pila
#Se libera el espacio solicitado en la pila
#fin de la función main
#Cadenas del programa
.data
.asciiz " Manejo de un arreglo \n"
.asciiz "Indica el tamaño del arreglo : "
.asciiz "Dame el número "
.asciiz " : "
.asciiz "\nLos numeros en orden inverso son: \n
.asciiz "\n
"
"
Los resultados arrojados en la consola para una corrida del programa son:
2.11.4 Configuración del programa PSIM
El programa PSIM permite configurar algunos parámetros; la consola de configuración se
encuentra en la opción settings del menú simulator, y tiene la siguiente forma:
En la ayuda del programa se describe el objetivo de cada una de las opciones de
configuración. El programa funciona correctamente con los valores preestablecidos.
Tarea 5
Esta tarea es para familiarizarse con el simulador SPIM, se entregará en un disco flexible o
en una memoria USB, para cada ejercicio se debe considerar:
i. El archivo con el código MIPS, con comentarios.
ii. En el código de cada programa se deberán incluir instrucciones que envíen su nombre y
grupo a la consola, al inicio o al final del programa.
iii. Un archivo en Word con la consola pegada, mostrando al menos tres ejemplos de
ejecución.
Ejercicios:
1.- El código que obtiene el menor de tres números inclúyalo en una función principal y
simúlelo.
2.- Realice la función principal para el programa SORT y simúlelo, recordar que debe
incluirse a la función SWAP (sug. Puede usarse la pila para almacenar el arreglo).
3.- Realice un programa que obtenga el mayor de un arreglo de n elementos proporcionados
por el usuario (sug. Usar la función desarrollada en la tarea anterior).
4.- Realice un programa que obtenga al n-ésimo termino de la serie de Fibonacci (sug. Usar
la función desarrollada en la tarea anterior).
Descargar