Beruflich Dokumente
Kultur Dokumente
Table of Contents
Problemas de aula 2. Funciones. Uso de la pila................................................................................................1
2.1 Introduccin.......................................................................................................................................1
2.2 Funciones...........................................................................................................................................1
2.3 Uso de la pila.....................................................................................................................................3
2.4 La direccin de retorno......................................................................................................................6
2.1 Introduccin
En esta sesin abordaremos diversos aspectos del mecanismo de invocacin a funciones y del paso de
parmetros.
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 (int
argc, char * argv[]) se ponga en ejecucin al inicio.
La funcin main() recibir los parmetros que el programa que ejecuta el binario (tpicamente el
intrprete 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 cada
funcin y la direccin de retorno.
2.2 Funciones
Empezaremos estudiando un programa simple que utiliza dos funciones: atof() y sqrt(). Cpialo en tu
editor Sera recomendable que crearas en tu directorio home un subdirectorio prog1, guardaras ah este
programa como prog1.c y trabajaras dentro de ese directorio.
/*
prog1.c */
#include <stdio.h>
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 */
Miramos la pgina del manual de la funcin atof() y vemos que tambin devuelve un tipo
double y no un entero como asume el compilador por omisin. Vemos adems que su
prototipo est en un fichero de cabecera determinado. Inclyelo en tu cdigo y compila de
nuevo.
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 los
parmetros y v. locales en la pila. De este modo, a cada variable se le asocia un
desplazamiento respecto a esta base y el compilador se halla libre de modificar el puntero de
pila.
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 programa
prog2.
A continuacin vamos a invocar a la funcin resta() directamente en ensamblador. Para
ello utilizaremos unas macros que nos van a permitir insertar directamente cdigo en
ensamblador en el programa en "C". La sintaxis de la directiva __asm__ la analizaremos en
la sesin PA5.
Las macros de las que disponemos son:
guarda_registros(): Almacena en la pila los registros %ebx, %ecx y %edx que se
modifican en la funcin resta().Como la funcin resta tiene cdigo directamente
en ensamblador, el compilador no es capaz de determinar que registros se ven
modificados por dicha funcin, y por lo tanto los tenemos que guardar nosotros antes
de 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 parmetros
sumndole 8 al puntero de pila) y almacena el valor de retorno en r. El valor de
retorno en los programas en "C" se suele almacenar en el registro %eax.
recupera_registros(): Recupera los registros almacenados antes de la invocacin de
la funcin resta().
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 la
declaracin 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__
__asm__
__asm__
__asm__
__asm__
__volatile__(
__volatile__(
__volatile__(
__volatile__(
__volatile__(
En el cdigo se puede observar que nosotros hemos utilizado la instruccin push para almacenar los
parmetros en la pila.
Si le echamos un vistazo al cdigo en ensamblador veremos que varia con respecto al generado por el
compilador.
8048496:
8048497:
8048498:
8048499:
804849a:
804849c:
804849d:
80484a2:
80484a5:
80484a6:
80484a7:
push
push
push
push
mov
push
call
add
pop
pop
pop
%ebx
%ecx
%edx
%eax
%ebx,%eax
%eax
80483d0 <resta>
$0x8,%esp
%edx
%ecx
%ebx
__volatile__(
__volatile__(
__volatile__(
__volatile__(
__volatile__(
En ensamblador quedara:
8048496:
8048497:
8048498:
8048499:
804849a:
804849c:
804849d:
80484a2:
80484a7:
80484aa:
80484ab:
80484ac:
push
push
push
push
mov
push
push
jmp
add
pop
pop
pop
%ebx
%ecx
%edx
%eax
%ebx,%eax
%eax
$0x80484a7
80483d0 <resta>
$0x8,%esp
%edx
%ecx
%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 funcin
retorne a un punto distinto del programa, en vez de a la instruccin siguiente. Esto se utiliza en el
ncleo de Linux para llevar a cabo parte del cambio de contexto.
__volatile__(
__volatile__(
__volatile__(
__volatile__(
En ensamblador quedara:
80484d2:
80484d3:
80484d4:
80484d5:
80484d6:
80484d8:
80484d9:
80484de:
80484e3:
80484ea:
push
push
push
push
mov
push
push
jmp
movl
call
%ebx
%ecx
%edx
%eax
%esi,%eax
%eax
$0x80484ef
8048410 <resta>
$0x804865a,(%esp)
80482f4 <printf>
080484ef <salida>:
80484ef:
add
$0x8,%esp
80484f2:
pop
%edx
80484f3:
pop
%ecx
80484f4:
pop
%ebx