2
Nov
2012

Punteros a funciones: desde C a Assembler

En C tenemos la posibilidad de pasar punteros a funciones como parámetros de una función. Esto permite realizar llamadas a la función-parámetro desde la función. Si por ejemplo lo combinamos con parámetros de tipo *void (para la función-parámetro), podría llegar a verse como una forma rústica de polimorfismo.

Ejemplo:

#include <stdlib.h>
#include <stdio.h>
 
int foo(int);
void bar(int (*)(int));
 
int main(int argc, char *argv[]){
 
	bar(foo);
 
	return 0;
}
 
int foo(int num){
	return num;
}
 
void bar(int (*func)(int)){
	int res = (*func)(3);
	printf("res = %d\n", res);
}

Ahora, veamos que está sucediendo a nivel Assembler:

080483c4 <main>:
 80483c4:	55                   	push   %ebp
 80483c5:	89 e5                	mov    %esp,%ebp
 80483c7:	83 e4 f0             	and    $0xfffffff0,%esp
 80483ca:	83 ec 10             	sub    $0x10,%esp
 80483cd:	c7 04 24 e0 83 04 08 	movl   $0x80483e0,(%esp)
 80483d4:	e8 0f 00 00 00       	call   80483e8 
 80483d9:	b8 00 00 00 00       	mov    $0x0,%eax
 80483de:	c9                   	leave
 80483df:	c3                   	ret

En la función Main se puede ver que se substraen 10 hexa-bytes al stack pointer (el stack crece). Una vez que hicimos lugar en el stack, cargamos allí la dirección 0x80483e0. Esta dirección es, precisamente, la dirección de memoria donde se encuentra la función “foo”, que será pasada como parámetro a la función “bar”. Luego simplemente se hace la llamada a la función “bar”.

080483e0 <foo>:
 80483e0:	55                   	push   %ebp
 80483e1:	89 e5                	mov    %esp,%ebp
 80483e3:	8b 45 08             	mov    0x8(%ebp),%eax
 80483e6:	5d                   	pop    %ebp
 80483e7:	c3                   	ret

La función foo no hace nada de particular interés.

080483e8 <bar>:
 80483e8:	55                   	push   %ebp
 80483e9:	89 e5                	mov    %esp,%ebp
 80483eb:	83 ec 28             	sub    $0x28,%esp
 80483ee:	c7 04 24 03 00 00 00 	movl   $0x3,(%esp)
 80483f5:	8b 45 08             	mov    0x8(%ebp),%eax
 80483f8:	ff d0                	call   *%eax
 80483fa:	89 45 f4             	mov    %eax,-0xc(%ebp)
 80483fd:	b8 e0 84 04 08       	mov    $0x80484e0,%eax
 8048402:	8b 55 f4             	mov    -0xc(%ebp),%edx
 8048405:	89 54 24 04          	mov    %edx,0x4(%esp)
 8048409:	89 04 24             	mov    %eax,(%esp)
 804840c:	e8 e3 fe ff ff       	call   80482f4

 8048411:	c9                   	leave

La función bar guarda el base pointer de main y fija su base pointer en el stack pointer de main. Por lo tanto, la función bar tiene de su base pointer hacia arriba, los parámetros recibidos y de su base pointer hacia abajo, espacio para hacer crecer a su stack. A continuación, hace crecer su stack restándole 28 hexa-bytes. Guarda 3 hexa en el stack, que es el parámetro que se enviará a la función-parámetro. Y ahora viene lo interesante: se toma del base pointer hacia arriba (porque fue un parámetro enviado por la función Main) la dirección de memoria de la función foo y la guarda en el registro eax. Luego se hace la llamada (negrita) utilizando esa dirección.

Escribir un comentario