580 likes | 794 Views
CLASE 17 FUNCIONES Y PASO DE PARÁMETROS A PROCEDIMIENTOS. Cuando se diseña un lenguaje de alto nivel ( en adelante llamado HLL ), usualmente se hace abstracción del sistema sobre el cual operará, es decir, un lenguaje dado se crea como si fuera a ejecutarse sobre un computador virtual.
E N D
Cuando se diseña un lenguaje de alto nivel (en adelante llamado HLL), usualmente se hace abstracción del sistema sobre el cual operará, es decir, un lenguaje dado se crea como si fuera a ejecutarse sobre un computador virtual.
Siendo las funciones unas de las herramientas más poderosas de cualquier lenguaje, el paso de parámetros entre ellas tampoco debe depender del tipo de máquina en particular en que se ejecute en un momento dado.
Se comentó antes que para transferir parámetros entre procedimientos se puede usar la pila o los registros. Cuando una función es genérica, se usará como camino de transferencia la pila. Se dirá entonces que una función es un procedimiento que recibe parámetros a través de la pila, usando un mecanismo estandarizado.
La forma en que los HLL transfieren parámetros a sus funciones a través de la pila se presenta en dos formatos: Apilamiento por izquierda Apilamiento por derecha APILAMIENTO:
Para explicar la diferencia se usará la función genérica: func (a, b, c) Se tiene que el orden IZQUIERDO es: a-b-c en tanto que el orden DERECHO será c-b-a.
Un HLL será apilador por la izquierda si ingresa los parámetros en la pila en orden izquierdo. Para el ejemplo, la pila quedaría así: SP c b a
Por el contrario, será apilador por la derecha si el orden derecho es el escogido. Este método es el que se tomará como supuesto en adelante (que es el caso de C): SP a b c
Después decolocar los parámetros, se invoca el procedimiento respectivo; el microprocesador se encarga de apilar la dirección de retorno. El aspecto actual de la pila es: IP de retorno SP a b c
Esa es la forma en que la pila está cuando se entra al procedimiento. Para acceder a los parámetros, es norma utilizar el registro BP, el cual permite direccionar a cualquier elemento dentro de la pila. Puesto que BP podría tener un valor anterior (llamado de una función desde otra) es común apilar también el valor actual de BP.
Direcciones relativas de cada parámetro al ser direccionado por BP (después de asignar a BP el valor de SP): antiguo BP BP = SP IP de retorno [BP+2] [BP+4] a [BP+6] b [BP+8] c
De acuerdo a lo anterior, la entrada y salida típicas de una función son: func PROC PUSH BP MOV BP, SP . . . . . . POP BP RETfunc ENDP PLANTILLA DE FUNCIÓN:
Prácticamente todos los HLL devuelven resultados a través del acumulador, usando los registros respectivos para acumulador de 8, 16 ó 32 bits. Cuando el resultado es mayor a 32 bits, se retorna un puntero al lugar donde el resultado ha quedado guardado. VALORES RETORNADOS:
Para visualizar mejor estas ideas se implementará la siguiente función, cuyo propósito en particular no interesa en el momento: char funcX(int a, char b){ return (a + 5) / b;} EJEMPLO:
Como es ya conocido, el procedimiento tiene una macro asociada para interfaz con el exterior. La macro se encarga de apilar los parámetros y de invocar al procedimiento. Puesto que se desconoce si los argumentos serán o no constantes (que no pueden apilarse directamente) es usual trabajar con el registro BX.
Los trabajos usuales de la macro son: Apilar BX para preservar su valor Llevar cada argumento a BX e irlo apilando en el orden escogido Invocar al procedimiento Quitar los argumentos de la pila Desapilar BX para restituir su valor original
funcX MACRO ArgA, ArgB PUSH BX ; preservar reg. MOV BL, ArgB; primer param. PUSH BX MOV BX, ArgA; segundo param. PUSH BX CALL pfuncX ; llamar proc. ADD SP, 4 ; limpiar pila POP BX ; restituir reg.ENDM ; funcX
Las labores normales de un procedimiento: Apilar BP para preservar su valor Copiar SP en BP Apilar los registros que vayan a utilizarse Realizar las tareas necesarias, usando a BP como puntero a los parámetros Llevar el resultado al Acc si es necesario Desapilar los registros apilados Desapilar BP Retornar
pfuncX PROC PUSH BP MOV BP, SP PUSH BX MOV AX, [BP+4] ADD AX, 5 MOV BX, [BP+6] IDIV BL POP BX POP BP RET pfuncX ENDP
Intercambio de dos variables Obtener el mayor de dos valores Conversión en mayúsculas de una cadena Ordenamiento de una lista por el método de la burbuja Función seno a partir de datos en tabla EJERCICIOS:
Además de las consideraciones dadas anteriormente sobre las funciones en general, algunos aspectos particulares requieren especial atención. Ellos son: Retorno de errores Interfaz con otros lenguajes Recursividad
Cuando se diseña una función, se debe tener en mente: Una solución general para la labor que hará la función Soluciones particulares para casos que no cubra la solución general Prever la posibilidad de errores y retornar un código de error indicándolo
Una función puede fallar en lograr su cometido por dos causas fundamentales: Los operandos no se ajustan al tipo o a las características esperadas (error de entrada) El resultado no se ajusta al tipo en que debe retornarse la solución (error de salida) CAUSAS DE ERROR:
Puede visualizarse esto mejor por un ejemplo. Se sabe que la función atoi debe convertir una cadena formada por caracteres numéricos a su número entero equivalente: atoi (“2345”) = 2345
Supóngase ahora que se pide realizar la conversión: atoi (“23Wq&”) Claramente, el argumento no se presta para la conversión y la función debe retornar un indicador del error.
Tradicionalmente, la solución es retornar un valor que no pertenezca al conjunto solución. Sin embargo, éste no es el caso, pues el rango de atoi es todo el conjunto de enteros. La función C retorna un 0 cuando el argumento no puede convertirse a un número, ¡pero el 0 también puede ser una solución verdadera!.
Debido a esto, cuando se invoca a atoi y se recibe como respuesta un 0, debe añadirse código que analice la solución. Podría ser algo como esto:
Debido a esto, cuando se invoca a atoi y se recibe como respuesta un 0, debe añadirse código que analice la solución. Podría ser algo como esto: x = atoi ( cadena ); if ( !x ) if ( !cadena[0] )/* Error */ else/* Respuesta == 0 */ else/* Respuesta != 0 */
El ensamblador permite una solución mejor a este problema, que no está disponible en ningún otro lenguaje. Una función de ensamblador puede retornar 2 valores: La solución como tal y un indicador de si se logró éxito o fracaso en la actividad. BANDERA DE CARRY:
La solución se retorna en el acumulador, en tanto que el indicador éxito/fracaso es la bandera de acarreo. Normalmente, esta bandera es fijada por el uP cuando hay sobreflujo aritmético. Pero puede también fijarse a voluntad del programador usando las órdenes: CLC = Fijar el acarreo en 0 STC = Fijar el acarreo en 1
Así, la función llamadora deberá revisar el valor del acarreo y deducir si hubo o no error, usando las órdenes de salto: JC = Saltar si el acarreo está en 1JNC = Saltar si el acarreo está en 0 Quedando la interacción como: CALL Atoi JC Error ; código para éxitoError: ; código para fracaso
La misma función sirve para evidenciar otro concepto. Supóngase que se solicita: atoi (“356712”); Este argumento sí es convertible, pero al hacerlo se sobrepasa el máximo permitido. De nuevo, la función C retorna aquí un 0, y ahora sí es realmente difícil diferenciar este error del anterior. CÓDIGOS DE ERROR:
Otra vez la solución será más adecuada en ensamblador. Cuando las causas de error son varias, se seguirá usando el acarreo para indicarlo, pero se usará el acumulador para explicar el tipo de error, conocido como un código de error. La asociación es: if (Carry==0)/* Solución está en Acc */ else/* Código de error está en Acc */
Para lograr un acople exitoso entre funciones que han sido elaboradas con diferentes lenguajes, de debe escoger uno de los lenguajes como el de referencia y someter el código escrito en otros lenguajes a los parámetros del escogido. Se presentan aspectos a considerar tales como: nombres de segmento, paso de parámetros, recepción de resultados, tipo de saltos, etc.
Dada la variedad de lenguajes, sólo se explicará formalmente cómo enlazar funciones hechas en ensamblador para ser invocadas desde un programa hecho en lenguaje C. Además de ser C el lenguaje más popular actualmente, su forma de enlazar diversos códigos es bastante diferente a la de otros lenguajes conocidos.
Se darán diversas recomendaciones tanto para el código a ser llamado, que será escrito en ensamblador, como para el código llamador, hecho en ANSI C. Se recalca que es C estándar y no C++, pues en este caso la forma de interacción es algo diferente.
Escribir la(s) funcion(es) a enlazar en un archivo que sólo contenga el segmento de código, sin segmentos de datos o de pila, pues se supone que el programa llamador ya los ha declarado. ARCHIVO ENSAMBLADOR:
Si las funciones son estructuradasnunca usarán variables globales sino sólo parámetros de la pila (que debe ser una sola para todas funciones). La función debe asumir que los parámetros se han colocado siguiendo el método de apilación por la derecha.
El nombre del segmento de código debe ser igual al usado por el programa llamador. El estándar impuesto por MicroSoft y que es seguido por la mayoría de los compiladores es denominarlo _TEXT. Si este no es el caso, simplemente se puede leer el archivo .MAP generado por el compilador y determinar el nombre que asigna a los segmentos.
Para que las funciones puedan ser accesadas desde el exterior del archivo, deben declararse como públicas al comienzo del segmento, en la forma: public NombreFuncion FUNCIONES:
C exige que las funciones que son externas (que están en otro archivo) empiecen su nombre por el caracter de subrayado. Así que las funciones deben declararse como: _NombreFunc PROC . . . _NombreFunc ENDP
Dado que C es un lenguaje sensible al tipo de letra, debe instruirse al ensamblador para que no cambie las letras a mayúsculas que es su opción por defecto. PARA EL ENSAMBLADOR:
Debe explicársele al compilador que la función a llamar está en otro archivo, es decir que es externa, al dar su prototipo: extern TipoRet NombreFunc(TipoParams); Para estar seguros que el código será compilado como C estándar, la extensión del archivo debe ser .C PARA EL COMPILADOR:
Una vez el ensamblador y el compilador hayan generado sus respectivos archivos .OBJ, se entregan al enlazador para obtener el producto ejecutable final. En principio, cualquier enlazador servirá, puesto que el lenguajes origen de los archivos ya no interesa. PARA EL ENLAZADOR:
Debe tenerse en cuenta que el programa C requiere un código de inicio que el fabricante ubica en un archivo como código ensamblado. Existe uno distinto para cada modelo de memoria. En principio, para simplificar las labores, puede hacerse uso de la utilidad de crear un proyecto que ofrecen todos los compiladores y colocar en él los archivos objeto obtenidos.
Anteriormente se mencionó que una macro era la parte pública de un procedimiento y como tal debía tener el nombre significativo. Esto es válido cuando la función va a ser llamada desde otra función assembly. Cuando la función va a llamarse desde otro lenguaje la idea cambia. NOMENCLATURA:
Cuando la función se invoca desde otro lenguaje, el compilador de ese lenguaje se encarga de generarla macro de llamado, así que el nombre significativo debe darse al procedimiento. Debe aclararse que esto es recomendable sólo cuando dicho procedimiento sea hecho exclusivamente para ser llamado desde otro lenguaje.