Punteros a función en C

Bueno, unas de las cosas que me quedan en el tintero es poner ejemplos de código. Con esta entrada espero poner fin a esto hablando de los punteros a función en C.

Muchos de los que hayan programado en C sabrán lo infernal que puede resultar el uso de punteros. Hoy, para rizar el rizo, voy a hablaros de los punteros a función.

Como sucede con los punteros normales (a variables), un puntero a función no es más que un apuntador que nos dice donde se encuentra una función. La pregunta ahora es… ¿Y para qué nos puede servir esto? Pues la verdad es que para muchas cosas, por ejemplo, cuando se dispone de un algoritmo de ordenación en C y queremos que este nos sirva para cualquier cosa, lo interesante es tener un mecanismo que nos permita comparar dos elementos. Podemos hacer el algortimo de modo que sólo soporte un tipo de datos o podemos hacer que soporte cualquier tipo. Esto lo hacemos con punteros a funciones. Vamos a ver un ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Ordena los elementos de una lista de menor a mayor.
* La lista es genérica y almacena sus elementos como punteros a cualquier cosa (void *).
* Se utiliza una función de comparación a la que se le pasan dos elementos de la lista y nos
* informa si el primero es mayor que el segundo.
*/
void ordenar( lista_t lista, int (*cmp)(void *elm1, void *elm2) ) {
   void *elem1, *elem2, *tmp;
   int numero_elementos = lista_numero_elementos( lista );
   int i, c;
 
   for( i = 0; i < numero_elementos; i ++ )
   {
      for( c = 0; c < numero_elementos-i-1; c ++ )
      {
         elem1 = lista_obtener_elemento( lista, c );
         elem2 = lista_obtener_elemento( lista, c+1 );
         /* Si elem1 > elem2 Entonces los intercambiamos.
         * Para hacer esta comparación, utilizamos la función del usuario. */
         if( cmp( elem1, elem2 ) )
         {
            lista_asignar_elemento( lista, c, elem2 );
            lista_asignar_elemento( lista, c+1, elem1 );
         } /* if( cmp( elem1, elem2 ) ) */
      } /* for( c = 0; c < numero_elementos-i-1; c ++ ) */
   } /* for( i = 0; i < numero_elementos; i ++ ) */
}
 
/**
* Compara elm1 y elm2, teniendo en cuenta que estos deben ser enteros.
* Si elm1 es mayor que elm2, devuelve un valor distinto que 0. En cualquier
* otro caso, devolverá 0.
*/
int CompararEnteros( void *elm1, void *elm2 )
{
   int a, b;
   a = (int)elm1;
   b = (int)elm2;
   return a > b;
}
 
/* Código del usuario */
 
/* Para ordenar la lista de enteros: */
ordenar( lista, CompararEnteros );
 
 
/* Código del usuario */

Bueno, espero que no sea necesario que explique qué hacen las funciones de la lista. Como vemos, con este mecanismo podemos usar enteros, flotantes, estructuras o imágenes. En verdad ahora podríamos ordenar una lista de cualquier cosa. Esto se consigue precisamente por el uso de punteros a función. Los que hayan hecho porgramación orientada a objetos se darán cuenta que esto se podría haber hecho con herencia.

En la línea 7, tenemos int (*cmp)(void *elm1, void *elm2). Esto es precisamente la declaración de una variable cmp que será un puntero a una función que devolverá un entero y que acepta dos parámetros de tipo void *. Para utilizar el puntero, sólo hay que hacer como si fuera una función normal y corriente:

cmp( param1, param2 );

En general, la definición de todo puntero a función es de la forma:

tipo_de_retorno (*nombre_de_la_variable)( parametros );

Para asignar a un puntero a función la dirección de un puntero, no hay más que hacer una signación normal:

puntero_a_función = función;

La función sería sin los argumentos.

Esto es sólo un ejemplo de lo que se puede hacer. Los punteros a funciones también pueden emplearse para crear menús dinámicos en los que queremos que, cuando se les pulse, se llame a una función (esta se pasaría como puntero a función). En muchas prácticas de las asignaturas de sistemas operativos, al utilizar señales tipo UNIX, se utilizan punteros a funciones. Se utilizan cuando decimos: “pues cuando llegue la señal SIGXXXX, quiero que llames a esta función”. Cuando se hace programación multihilo (o con threads), también se usan los punteros a función.

Bueno, esto ha sido una pequeña introducción. Si a alguien le interesa saber más, puede decirlo en los comentarios. Si se me ocurre poner algo más, ya lo haré. Espero que os haya servido de ayuda.

5 comentarios en “Punteros a función en C

  1. Debo de estar un poco dormido o atrofiado de tan poco que programo, te quería hacer una preguntilla (espero que no parezca demasiado idiota :P ):

    En la cabecera de la función ordenar, ¿qué significa este parámaetro int (*cmp)(void *elm1, void *elm2)?

    Entiendo que (*cmp) será el puntero a la función, y que el resto (void *elm1, void *elm2) son los parámetros, pero no entiendo el int de delante, ni porque encierras entre paréntesis al *cmp.

    SaludoS

  2. Vale, si es que siempre se le olvida a alguno cosas. Bien, la definición de un puntero a variable es siempre de la forma:

    tipo_de_retorno (*nombre_puntero) ( parametros_que_tiene );

    La idea de hacer el ‘int (*cmp)’ es que si pones ‘int *cmp’ sería una función que devuelve un puntero a entero. Lo otro es un puntero a una función que devolverá un entero.

    De todos modos, ahora lo cambiaré.

  3. Hay un pequeño problema y es que tu código no es ’64bit safe’. Estás haciendo una conversión ilegal ‘void *’ -> ‘int’.

    En ‘CompararEnteros’ el código sería algo como:

    int pa = (int *)elm1;
    int a = *pa;

    Y no como está ahí.

    Por lo demás simplemente dejar un buen tutorial al respecto: http://www.newty.de/fpt/index.html

    – ferdy

  4. Bueno, sí es probable (o seguro) que no es ’64bit safe’. Para el que no sepa a qué se refiere esto, es que, en las arquitecturas de 64 bits, los punteros son de 64 bits, mientras que los enteros de tipo ‘int’ son (dependiendo del compilador) de 32 bits.

    Por lo anterior, se desprende que podría darse un problema con el código que he mostrado, como muy bien nos indica Ferdy. Debería probarlo, pero creo que no fallaría, ya que, a grandes rasgos, la cosa sería así:

    void *ptr;
    int a = 5;

    ptr = (void *)a;
    a = (int)ptr;

    Aunque ‘ptr’ sea de 64 bits, la cosa debería funcionar bien. Vale, estoy convencido de que funciona bien con valores positivos, pero no estoy seguro de cómo irá con valores negativos. Mañana haré una prueba y ya os comentaré.

    Por estas cosas prefiero la programación orienta a objetos (aunque C++ no me gusta :^P).

    P.D.: Gracias por el enlace, Ferdy.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *