9
ESO - Problemas de Aula Juan Carlos Pérez y Sergio Sáez

pa4-funciones

Embed Size (px)

DESCRIPTION

practica asignatura eso

Citation preview

  • ESO Problemas de AulaJuan Carlos Prez y Sergio Sez

  • Table of ContentsProblemas de aula 2. Funciones. Uso de la pila................................................................................................1

    2.1 Introduccin.......................................................................................................................................12.2 Funciones...........................................................................................................................................12.3 Uso de la pila.....................................................................................................................................32.4 La direccin de retorno......................................................................................................................6

    ESO Problemas de Aula

    i

  • Problemas de aula 2. Funciones. Uso de la pila.

    Juan Carlos Prez &Sergio Sez

    Sergio SezJuan Carlos Prez

    [email protected]@disca.upv.es

    2.1 Introduccin

    En esta sesin abordaremos diversos aspectos del mecanismo de invocacin a funciones y del paso deparmetros.

    El concepto de funcin es bsico en C. El cdigo de inicializacin generado por el compilador(enlazado por el cargador) se encarga de que la funcin principal del programa int main (intargc, char * argv[]) se ponga en ejecucin al inicio.

    La funcin main() recibir los parmetros que el programa que ejecuta el binario (tpicamente elintrprete de rdenes) le haya pasado a la llamada al sistema exec(3).

    La pila del proceso es el lugar donde se guardan estos parmetros, las variables locales de cadafuncin y la direccin de retorno.

    2.2 Funciones

    Empezaremos estudiando un programa simple que utiliza dos funciones: atof() y sqrt(). Cpialo en tueditor Sera recomendable que crearas en tu directorio home un subdirectorio prog1, guardaras ah esteprograma como prog1.c y trabajaras dentro de ese directorio.

    /* prog1.c */

    #include

    int main (int argc, char * argv[]) { double valor;

    printf("Argumento 1 (texto): %s\n", argv[1]); valor= atof(argv[1]); printf("Argumento 1 (nmero): %.2f\n", valor); printf("Funcin: sqrt(%.2f) = %.2f\n", valor, sqrt(valor)); return 0;} /* end main */

    Compila el programa: gcc prog1.c o prog1 Observa el error que aparece. Intenta entenderlo.

    prog1.c:20: warning: type mismatch in implicit declaration

    Problemas de aula 2. Funciones. Uso de la pila. 1

    mailto:[email protected]:[email protected]

  • for builtin function `sqrt'undefined reference to `sqrt'collect2: ld returned 1 exit status

    El compilador de C considera por omisin (cuando la funcin no se ha definido en ese fichero y no seha declarado, especificando los detalles, mediante un prototipo) que las funciones devuelven unentero y esperan recibir el tipo de parmetros que se le pasan. Una funcin se declara cuando seespecifica su tipo y los tipos de sus parmetros y se define cuando adems se especifica su cdigo.Una funcin puede declararse muchas veces pero slo puede definirse una vez. La declaracin de unafuncin en C se denomina tambin prototipo.

    El enlazador espera encontrar definidas todas las funciones en alguno de los ficheros objeto obibliotecas que se le especifican.

    Para que encuentre la funcin sqrt() hay que incluir en la lnea de compilacin, comovimos en la sesin anterior, la opcin de enlazar con la biblioteca matemtica. (Si norecuerdas cmo hacerlo, busca en el enunciado de la sesin anterior)

    Ahora slo tendremos un "warning", el mismo que en la compilacin anterior, pero elejecutable se crear. Analiza los resultados ejecutando el programa primero sin argumentos ydespus con un argumento numrico. Por qu se produce cada uno de estos errores defuncionamiento?

    $ prog1Argumento 1 (texto): (null)Segmentation fault

    $ prog1 16Argumento 1 (texto): 16Argumento 1 (nmero): 0.00Funcin: sqrt(0.00) = 0.00

    Lo primero es entender el "warning". Efectivamente, el compilador asume que sqrtdevuelve un entero y por tanto interpreta mal el valor de retorno de dicha funcin.

    Miramos la pgina de manual de sqrt y vemos que su prototipo est en el fichero decabecera math.h. Aade el correspondiente #include en el programa. El fichero math.hse encuentra en un directorio estndar de ficheros de cabecera. Se lo hemos de indicar alpreprocesador de C ponindolo entre < y >. Si estuviera en el directorio actual loescribiramos entre comillas.

    Compila y ejecuta el programa con un argumento numrico Da algn aviso el compilador? Funciona bien el programa? Cul crees que es la razn de este funcionamiento?

    Existe una opcin muy til del compilador que nunca est de ms activar: Wall . Con ellapedimos que se generen todos los avisos, aun los a priori menos importantes. Prueba a incluiresta opcin en la compilacin. Puedes aadir cualquier opcin en cualquier punto de la lneade compilacin excepto, por supuesto, entre o y prog1

    prog1b.c:18: warning: implicit declaration of function `atof'

    Miramos la pgina del manual de la funcin atof() y vemos que tambin devuelve un tipodouble y no un entero como asume el compilador por omisin. Vemos adems que suprototipo est en un fichero de cabecera determinado. Inclyelo en tu cdigo y compila denuevo.

    ESO Problemas de Aula

    Problemas de aula 2. Funciones. Uso de la pila. 2

  • El programa ya funciona cuando lo invocamos con un argumento numrico, pero fallar alinvocarlo sin argumentos.

    Modifcalo para que compruebe el nmero de argumentos recibido y si no es el que esperaimprima algo como:

    $ prog1Uso: prog1 Calcula la raiz cuadrada de

    2.3 Uso de la pila

    Sabemos que la pila es la estructura que el compilador emplea para pasar los parmetros a lasfunciones y almacenar las variables locales. Vamos a ver en la prctica cmo funciona estemecanismo y a hacer algunas pruebas para entenderlo mejor.

    Crea un nuevo subdirectorio prog2 y bjate los ficheros funciones.c, funciones.h y pila.h. Examina estos ficheros:

    En los dos primeros se define la funcin resta() que simplemente resta dosnmeros y muestra el contenido de la pila (3 enteros a partir del puntero de pila).

    En el tercero se definen una serie de macros para acceder a la pila, modificarla, saltara una funcin usando instrucciones de salto a subrutina y salto incondicional, etc.

    Copia en tu editor el siguiente programa:

    /* prog2.c */#include #include #include "funciones.h"#include "pila.h"

    int main (int argc, char * argv[]) { int e; int p1, p2;

    p1= atoi(argv[1]); p2= atoi(argv[2]); e= resta(p1,p2); printf("resta(%d,%d) = %d\n", p1, p2, e); return 0;} /* end main */

    Compila y ejecuta prog2. Se recomienda utilizar las opciones del compilador Wall O3.Funciona correctamente? Eres capaz de interpretar el resultado?

    El compilador usa el registro EBP como frame pointer, es decir, como base para indexar losparmetros y v. locales en la pila. De este modo, a cada variable se le asocia undesplazamiento respecto a esta base y el compilador se halla libre de modificar el puntero depila.

    ESO Problemas de Aula

    2.3 Uso de la pila 3

  • Utilizando la herramienta objdump podemos echarle un vistazo al cdigo en ensambladorgenerado para invocar a la funcin resta().

    ; Con anterioridad se ha preparado un hueco en la pila con una ; instruccin del tipo "sub $0x8, %esp"8048494: 89 44 24 04 mov %eax,0x4(%esp) ; Copia en la pila el parmetro 28048498: 89 c3 mov %eax,%ebx ; Almacena %eax en un registro ; puesto que el registro %eax es ; el de retorno804849a: 89 34 24 mov %esi,(%esp) ; Copia en la pila el parmetro 1804849d: e8 2e ff ff ff call 80483d0 ; Invocacin de la funcin resta80484a2: ; Direccin de retorno

    Vemos que el compilador no utiliza la instruccin push para meter los parmetros en la pila,si no que hace alguna optimizacin.

    Observa que la direccin de retorno debera coincidir con la mostrada al ejecutar el programaprog2.

    A continuacin vamos a invocar a la funcin resta() directamente en ensamblador. Paraello utilizaremos unas macros que nos van a permitir insertar directamente cdigo enensamblador en el programa en "C". La sintaxis de la directiva __asm__ la analizaremos enla sesin PA5.

    Las macros de las que disponemos son:guarda_registros(): Almacena en la pila los registros %ebx, %ecx y %edx que semodifican en la funcin resta().Como la funcin resta tiene cdigo directamenteen ensamblador, el compilador no es capaz de determinar que registros se venmodificados por dicha funcin, y por lo tanto los tenemos que guardar nosotros antesde invocar a la funcin.

    parametro(p): Inserta el parmetro p en la pila. invoca(f,r): Invoca a la funcin f (utilizando la instruccin en ensamblador call),reestablece el estado de la pila (elimina el espacio ocupado por los parmetrossumndole 8 al puntero de pila) y almacena el valor de retorno en r. El valor deretorno en los programas en "C" se suele almacenar en el registro %eax.

    recupera_registros(): Recupera los registros almacenados antes de la invocacin dela funcin resta().

    ESO Problemas de Aula

    2.3 Uso de la pila 4

  • As pues, el cdigo de la invocacin a la funcin:

    e= resta(p1,p2);

    quedara:

    guarda_registros(); parametro(p2); parametro(p1); invoca(resta, e); recupera_registros();

    Es interesante destacar que los parmetros se insertan en la pila en orden inverso al que aparecen en ladeclaracin de la funcin resta().

    Compila y ejecuta el programa con los dos argumentos numricos. Da algn aviso el compilador?Funciona el programa correctamente?

    Si utilizamos la opcin E del compilador podremos observar el cdigo generado finalmente en "C".

    int main (int argc, char * argv[]) {

    int e;

    int p1, p2; p1= atoi(argv[1]); p2= atoi(argv[2]);

    __asm__ __volatile__( "pushl %ebx\n" "pushl %ecx\n" "pushl %edx\n" ); __asm__ __volatile__( "push %%eax\n" : : "a" (p2) ); __asm__ __volatile__( "push %%eax\n" : : "a" (p1) ); __asm__ __volatile__( "call ""resta""\n" "add $0x8, %%esp\n" : "=a" (e) ); __asm__ __volatile__( "popl %edx\n" "popl %ecx\n" "popl %ebx\n" );

    printf("resta(%d,%d) = %d\n", p1, p2, e);

    return 0;}

    En el cdigo se puede observar que nosotros hemos utilizado la instruccin push para almacenar losparmetros en la pila.

    Si le echamos un vistazo al cdigo en ensamblador veremos que varia con respecto al generado por elcompilador.

    8048496: push %ebx ; Almacena algunos registros8048497: push %ecx8048498: push %edx8048499: push %eax ; Inserta en la pila el parmetro 2804849a: mov %ebx,%eax804849c: push %eax ; Inserta en la pila el parmetro 1804849d: call 80483d0 ; Invocacin de la funcin resta80484a2: add $0x8,%esp ; Elimina los parmetros de la pila80484a5: pop %edx ; Reestablece los registros80484a6: pop %ecx80484a7: pop %ebx

    La direccin de retorno debera coincidir con la mostrada al ejecutar el programa prog2.

    ESO Problemas de Aula

    2.3 Uso de la pila 5

  • 2.4 La direccin de retorno

    La instruccin en ensamblador call introduce automticamente la direccin de retorno en la pila, deforma que la instruccin ret al final de cada funcin sepa dnde debe continuar la ejecucin delprograma.

    Este comportamiento se puede "simular" introduciendo la direccin de retorno en la pila manualmente(con un push) y utilizando una instruccin jmp para saltar a la direccin de comienzo de la funcin.

    Cuando se ejecute la instruccin ret volver a la direccin que encuentre en la pila. Elcomportamiento ser idntico al que tendra se se utilizar la instruccin call.

    Vamos a comprobar este comportamiento mediante el uso de la macro invoca_salto(f,r). Esta macrose comprta igual que la macro invoca(), pero gener un cdigo en ensamblador que utiliza lainstruccin jmp.

    Modificad el cdigo de prog2 para que utilice la macro invoca_salto(). Si mostramos el cdigo preprocesado se puede observar el uso de la etiqueta $1f para determinar ladireccin de retorno.

    ... __asm__ __volatile__( "pushl %ebx\n" "pushl %ecx\n" "pushl %edx\n" ); __asm__ __volatile__( "push %%eax\n" : : "a" (p2) ); __asm__ __volatile__( "push %%eax\n" : : "a" (p1) ); __asm__ __volatile__( "pushl $1f\n" "jmp ""resta""\n" "1:" "add $0x8, %%esp\n" : "=a" (e) ); __asm__ __volatile__( "popl %edx\n" "popl %ecx\n" "popl %ebx\n" );

    printf("resta(%d,%d) = %d\n", p1, p2, e);...

    En ensamblador quedara:

    8048496: push %ebx ; Almacena algunos registros8048497: push %ecx8048498: push %edx8048499: push %eax ; Inserta en la pila el parmetro 2804849a: mov %ebx,%eax804849c: push %eax ; Inserta en la pila el parmetro 1804849d: push $0x80484a7 ; Inserta la direccin de retorno80484a2: jmp 80483d0 ; Salta a la direccin de la funcin80484a7: add $0x8,%esp ; Elimina los parmetros de la pila80484aa: pop %edx ; Reestablece los registros80484ab: pop %ecx80484ac: pop %ebx

    Si compilamos y ejecutamos la nueva versin del programa, el comportamiento debera ser el mismo.

    Aunque este comportamiento no tiene una utilidad aparente, se puede utilizar para que una funcinretorne a un punto distinto del programa, en vez de a la instruccin siguiente. Esto se utiliza en elncleo de Linux para llevar a cabo parte del cambio de contexto.

    ESO Problemas de Aula

    2.4 La direccin de retorno 6

  • Modificando nuestro pragrama prog2 para comprobar este comportamiento, el progrma quedaacomo sigue:

    ... guarda_registros(); parametro(p2); parametro(p1); salto(resta, salida);

    printf("Este mensaje no se imprime\n");

    destino(salida, e); recupera_registros(); printf("Salida alternativa= resta(%d,%d) = %d\n", p1, p2, e);...

    Si compilamos y ejecutamos la nueva versin: Cul es el comportamiento? Se ejecuta la funcinprintf(...) que hay a continuacin de la macro salto()?

    El contenido final del programa en "C" una vez preprocesado sera:

    ... __asm__ __volatile__( "pushl %ebx\n" "pushl %ecx\n" "pushl %edx\n" ); __asm__ __volatile__( "push %%eax\n" : : "a" (p2) ); __asm__ __volatile__( "push %%eax\n" : : "a" (p1) ); __asm__ __volatile__( "pushl $""salida""\n" "jmp ""resta""\n" );

    printf("Este mensaje no se imprime\n");

    __asm__ __volatile__( "salida"":\n" "add $0x8, %%esp\n" : "=a" (e) ); __asm__ __volatile__( "popl %edx\n" "popl %ecx\n" "popl %ebx\n" ); printf("Salida alternativa= resta(%d,%d) = %d\n", p1, p2, e);...

    En ensamblador quedara:

    80484d2: push %ebx ; Almacena algunos registros80484d3: push %ecx80484d4: push %edx80484d5: push %eax ; Inserta en la pila el parmetro 280484d6: mov %esi,%eax80484d8: push %eax ; Inserta en la pila el parmetro 180484d9: push $0x80484ef ; Inserta la direccin de retorno80484de: jmp 8048410 ; Salta a la direccin de la funcin80484e3: movl $0x804865a,(%esp) ; Este cdigo no se ejecuta80484ea: call 80482f4

    080484ef :80484ef: add $0x8,%esp ; Elimina los parmetros de la pila80484f2: pop %edx ; Reestablece los registros80484f3: pop %ecx80484f4: pop %ebx

    ESO Problemas de Aula

    2.4 La direccin de retorno 7

    Table of ContentsProblemas de aula 2. Funciones. Uso de la pila.2.1 Introduccin2.2 Funciones2.3 Uso de la pila2.4 La direccin de retorno