Pregunta

Últimamente tuve algo de experiencia con punteros de función en C.

Entonces, siguiendo con la tradición de responder tus propias preguntas, decidí hacer un pequeño resumen de los conceptos básicos, para aquellos que necesitan una inmersión rápida en el tema.

¿Fue útil?

Solución

Punteros de función en C

Comencemos con una función básica a la que estaremos apuntando :

int addInt(int n, int m) {
    return n+m;
}

Primero, definamos un puntero a una función que recibe 2 int sy devuelve un typedef:

int (*functionPtr)(int,int);

Ahora podemos señalar con seguridad nuestra función:

functionPtr = &addInt;

Ahora que tenemos un puntero a la función, usémosla:

int sum = (*functionPtr)(2, 3); // sum == 5

Pasar el puntero a otra función es básicamente lo mismo:

int add2to3(int (*functionPtr)(int, int)) {
    return (*functionPtr)(2, 3);
}

También podemos usar punteros de función en los valores de retorno (intente mantener el ritmo, se vuelve desordenado):

// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
    printf("Got parameter %d", n);
    int (*functionPtr)(int,int) = &addInt;
    return functionPtr;
}

Pero es mucho mejor usar un <=>:

typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef

myFuncDef functionFactory(int n) {
    printf("Got parameter %d", n);
    myFuncDef functionPtr = &addInt;
    return functionPtr;
}

Otros consejos

Los punteros de función en C se pueden utilizar para realizar programación orientada a objetos en C.

Por ejemplo, las siguientes líneas están escritas en C:

String s1 = newString();
s1->set(s1, "hello");

Sí el -> y la falta de un new El operador es un claro indicio, pero seguro parece implicar que estamos configurando el texto de algún String clase para ser "hello".

Al utilizar punteros de función, es posible emular métodos en C.

¿Cómo se logra esto?

El String la clase es en realidad una struct con un montón de punteros de función que actúan como una forma de simular métodos.La siguiente es una declaración parcial de la String clase:

typedef struct String_Struct* String;

struct String_Struct
{
    char* (*get)(const void* self);
    void (*set)(const void* self, char* value);
    int (*length)(const void* self);
};

char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);

String newString();

Como se puede observar, los métodos de la String class son en realidad punteros de función a la función declarada.Al preparar la instancia del String, el newString Se llama a la función para configurar los punteros de función a sus respectivas funciones:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

Por ejemplo, el getString función que se llama invocando el get método se define de la siguiente manera:

char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

Una cosa que se puede notar es que no existe el concepto de una instancia de un objeto y de tener métodos que en realidad sean parte de un objeto, por lo que se debe pasar un "objeto propio" en cada invocación.(Y el internal es solo un oculto struct que se omitió en la lista de códigos anterior; es una forma de ocultar información, pero no es relevante para los punteros de función).

Entonces, en lugar de poder hacer s1->set("hello");, se debe pasar el objeto para realizar la acción en s1->set(s1, "hello").

Una vez que esa pequeña explicación tenga que incluir una referencia a usted mismo, pasaremos a la siguiente parte, que es herencia en C.

Digamos que queremos hacer una subclase de String, dice un ImmutableString.Para hacer que la cadena sea inmutable, el set método no será accesible, mientras se mantiene el acceso a get y length, y obligar al "constructor" a aceptar un char*:

typedef struct ImmutableString_Struct* ImmutableString;

struct ImmutableString_Struct
{
    String base;

    char* (*get)(const void* self);
    int (*length)(const void* self);
};

ImmutableString newImmutableString(const char* value);

Básicamente, para todas las subclases, los métodos disponibles son nuevamente punteros de función.Esta vez, la declaración de la set El método no está presente, por lo tanto, no se puede llamar en un ImmutableString.

En cuanto a la implementación del ImmutableString, el único código relevante es la función "constructora", la newImmutableString:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = self->base->length;

    self->base->set(self->base, (char*)value);

    return self;
}

Al instanciar el ImmutableString, la función apunta a la get y length Los métodos en realidad se refieren a la String.get y String.length método, pasando por el base variable que se almacena internamente String objeto.

El uso de un puntero de función puede lograr la herencia de un método de una superclase.

Podemos continuar más polimorfismo en C.

Si por ejemplo quisiéramos cambiar el comportamiento del length método para regresar 0 todo el tiempo en el ImmutableString clase por alguna razón, todo lo que habría que hacer es:

  1. Agregue una función que servirá como anulación length método.
  2. Vaya al "constructor" y establezca el puntero de función en el elemento primordial length método.

Agregar una anulación length método en ImmutableString puede realizarse añadiendo un lengthOverrideMethod:

int lengthOverrideMethod(const void* self)
{
    return 0;
}

Entonces, el puntero de función para el length El método en el constructor está conectado al lengthOverrideMethod:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = &lengthOverrideMethod;

    self->base->set(self->base, (char*)value);

    return self;
}

Ahora bien, en lugar de tener un comportamiento idéntico para el length método en ImmutableString clase como el String clase, ahora el length El método se referirá al comportamiento definido en el lengthOverrideMethod función.

Debo agregar un descargo de responsabilidad de que todavía estoy aprendiendo a escribir con un estilo de programación orientada a objetos en C, por lo que probablemente hay puntos que no expliqué bien, o que simplemente pueden estar fuera de lugar en términos de la mejor manera de implementar la programación orientada a objetos. Cª.Pero mi propósito era intentar ilustrar uno de los muchos usos de los punteros de función.

Para obtener más información sobre cómo realizar programación orientada a objetos en C, consulte las siguientes preguntas:

La guía para ser despedido:Cómo abusar de los punteros de función en GCC en máquinas x86 compilando su código a mano:

Estos literales de cadena son bytes de código de máquina x86 de 32 bits. 0xC3 es un x86 ret instrucción.

Normalmente no los escribirías a mano, los escribirías en lenguaje ensamblador y luego usarías un ensamblador como nasm para ensamblarlo en un binario plano que se volca hexadecimalmente en un literal de cadena C.

  1. Devuelve el valor actual en el registro EAX.

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. Escribe una función de intercambio

    int a = 10, b = 20;
    ((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
    
  3. Escriba un contador de bucle hasta 1000, llamando a alguna función cada vez

    ((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
    
  4. Incluso puedes escribir una función recursiva que cuente hasta 100.

    const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
    i = ((int(*)())(lol))(lol);
    

Tenga en cuenta que los compiladores colocan cadenas literales en el .rodata sección (o .rdata en Windows), que está vinculado como parte del segmento de texto (junto con el código de funciones).

El segmento de texto tiene permiso Read+Exec, por lo que convertir literales de cadena en punteros de función funciona sin necesidad mprotect() o VirtualProtect() llamadas al sistema como las que necesitaría para la memoria asignada dinámicamente.(O gcc -z execstack vincula el programa con pila + segmento de datos + ejecutable del montón, como un truco rápido).


Para desensamblarlos, puede compilar esto para poner una etiqueta en los bytes y usar un desensamblador.

// at global scope
const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";

Compilando con gcc -c -m32 foo.c y desarmar con objdump -D -rwC -Mintel, podemos obtener el ensamblado y descubrir que este código viola la ABI al dañar EBX (un registro de llamadas preservadas) y generalmente es ineficiente.

00000000 <swap>:
   0:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]   # load int *a arg from the stack
   4:   8b 5c 24 08             mov    ebx,DWORD PTR [esp+0x8]   # ebx = b
   8:   8b 00                   mov    eax,DWORD PTR [eax]       # dereference: eax = *a
   a:   8b 1b                   mov    ebx,DWORD PTR [ebx]
   c:   31 c3                   xor    ebx,eax                # pointless xor-swap
   e:   31 d8                   xor    eax,ebx                # instead of just storing with opposite registers
  10:   31 c3                   xor    ebx,eax
  12:   8b 4c 24 04             mov    ecx,DWORD PTR [esp+0x4]  # reload a from the stack
  16:   89 01                   mov    DWORD PTR [ecx],eax     # store to *a
  18:   8b 4c 24 08             mov    ecx,DWORD PTR [esp+0x8]
  1c:   89 19                   mov    DWORD PTR [ecx],ebx
  1e:   c3                      ret    

  not shown: the later bytes are ASCII text documentation
  they're not executed by the CPU because the ret instruction sends execution back to the caller

Este código de máquina (probablemente) funcionará en código de 32 bits en Windows, Linux, OS X, etc.:las convenciones de llamadas predeterminadas en todos esos sistemas operativos pasan argumentos en la pila en lugar de hacerlo de manera más eficiente en los registros.Pero EBX conserva las llamadas en todas las convenciones de llamadas normales, por lo que usarlo como un registro temporal sin guardarlo o restaurarlo puede hacer que la persona que llama falle fácilmente.

Uno de mis usos favoritos para los punteros de función es como iteradores económicos y fáciles:

#include <stdio.h>
#define MAX_COLORS  256

typedef struct {
    char* name;
    int red;
    int green;
    int blue;
} Color;

Color Colors[MAX_COLORS];


void eachColor (void (*fp)(Color *c)) {
    int i;
    for (i=0; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}

int main() {
    Colors[0].name="red";
    Colors[0].red=255;
    Colors[1].name="blue";
    Colors[1].blue=255;
    Colors[2].name="black";

    eachColor(printColor);
}

Los punteros a funciones se vuelven fáciles de declarar una vez que se tienen los declaradores básicos:

  • identificación: ID: la identificación es una
  • Puntero: *D: puntero D a
  • Función: D(<parameters>): Toma de función D <parámetros> regresando

Mientras que D es otro declarador creado utilizando esas mismas reglas.Al final, en algún lugar, termina con ID (ver más abajo un ejemplo), que es el nombre de la entidad declarada.Intentemos construir una función que tome un puntero a una función que no tome nada y devuelva int, y devuelva un puntero a una función que tome un char y devuelva int.Con type-defs es así

typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

Como puede ver, es bastante fácil crearlo usando typedefs.Sin typedefs, tampoco es difícil con las reglas del declarador anteriores, aplicadas de manera consistente.Como puede ver, me perdí la parte a la que apunta el puntero y lo que devuelve la función.Esto es lo que aparece en el extremo izquierdo de la declaración, y no interesa:Se agrega al final si ya se creó el declarador.Vamos a hacer eso.Construyéndolo consistentemente, primero con palabras: mostrando la estructura usando [ y ]:

function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

Como puede ver, se puede describir un tipo completamente añadiendo declaradores uno tras otro.La construcción se puede realizar de dos formas.Uno es de abajo hacia arriba, comenzando con lo correcto (hojas) y avanzando hasta el identificador.La otra forma es de arriba hacia abajo, comenzando en el identificador y avanzando hasta las hojas.Mostraré ambos sentidos.

De abajo hacia arriba

La construcción comienza con lo de la derecha:Lo devuelto, que es la función que toma char.Para mantener distintos los declaradores, los voy a numerar:

D1(char);

Inserté el parámetro char directamente, ya que es trivial.Agregar un puntero al declarador reemplazando D1 por *D2.Tenga en cuenta que tenemos que envolver paréntesis *D2.Esto se puede saber buscando la precedencia de la *-operator y el operador de llamada de función ().Sin nuestros paréntesis, el compilador lo leería como *(D2(char p)).Pero eso no sería un simple reemplazo de D1 por *D2 más, por supuesto.Siempre se permiten paréntesis alrededor de los declaradores.Así que, de hecho, no comete ningún error si agrega demasiados.

(*D2)(char);

¡El tipo de devolución está completo!Ahora, reemplacemos D2 por el declarador de funciones toma de funciones <parameters> regresando, cual es D3(<parameters>) en el que nos encontramos ahora.

(*D3(<parameters>))(char)

Tenga en cuenta que no se necesitan paréntesis, ya que desear D3 Esta vez será un declarador de función y no un declarador de puntero.Genial, lo único que queda son los parámetros.El parámetro se hace exactamente igual que hemos hecho con el tipo de retorno, solo que con char reemplazado por void.Así que lo copiaré:

(*D3(   (*ID1)(void)))(char)

he reemplazado D2 por ID1, ya que hemos terminado con ese parámetro (ya es un puntero a una función, no es necesario otro declarador). ID1 será el nombre del parámetro.Ahora, como dije anteriormente, al final se agrega el tipo que modifican todos los declarantes: el que aparece en el extremo izquierdo de cada declaración.Para funciones, ese se convierte en el tipo de retorno.Para punteros, el tipo señalado, etc.Es interesante que cuando escriba el tipo, aparecerá en el orden opuesto, muy a la derecha :) De todos modos, al sustituirlo se obtiene la declaración completa.Ambas veces int por supuesto.

int (*ID0(int (*ID1)(void)))(char)

He llamado al identificador de la función. ID0 en ese ejemplo.

De arriba hacia abajo

Esto comienza en el identificador en el extremo izquierdo de la descripción del tipo, envolviendo ese declarador a medida que avanzamos hacia la derecha.Empezar con toma de funciones <parámetros> regresando

ID0(<parameters>)

Lo siguiente en la descripción (después de "devolver") fue puntero a.Incorporémoslo:

*ID0(<parameters>)

Entonces lo siguiente fue toma de funciones <parámetros> regresando.El parámetro es un carácter simple, por lo que lo volvemos a poner de inmediato, ya que es realmente trivial.

(*ID0(<parameters>))(char)

Tenga en cuenta los paréntesis que agregamos, ya que nuevamente queremos que el * se une primero, y entonces el (char).De lo contrario se leería toma de funciones <parámetros> función de retorno....No, las funciones que devuelven funciones ni siquiera están permitidas.

Ahora solo nos falta poner <parámetros>.Mostraré una versión corta de la derivación, ya que creo que ya tienes la idea de cómo hacerlo.

pointer to: *ID1
... function taking void returning: (*ID1)(void)

Sólo hay que poner int antes de los declaradores como lo hicimos con el método ascendente, y hemos terminado

int (*ID0(int (*ID1)(void)))(char)

lo bueno

¿Es mejor de abajo hacia arriba o de arriba hacia abajo?Estoy acostumbrado a hacerlo de abajo hacia arriba, pero algunas personas pueden sentirse más cómodas con lo de arriba hacia abajo.Es una cuestión de gustos, creo.Por cierto, si aplicas todos los operadores en esa declaración, terminarás obteniendo un int:

int v = (*ID0(some_function_pointer))(some_char);

Esa es una buena propiedad de las declaraciones en C:La declaración afirma que si esos operadores se utilizan en una expresión que utiliza el identificador, se obtiene el tipo de la izquierda.También es así para las matrices.

¡Espero que os haya gustado este pequeño tutorial!Ahora podemos vincularnos a esto cuando la gente se pregunta acerca de la extraña sintaxis de declaración de funciones.Intenté poner la menor cantidad posible de componentes internos de C.Siéntete libre de editar/arreglar cosas en él.

Otro buen uso de los punteros de función:
Cambiar entre versiones sin problemas

Son muy útiles cuando deseas diferentes funciones en diferentes momentos o diferentes fases de desarrollo.Por ejemplo, estoy desarrollando una aplicación en una computadora host que tiene una consola, pero la versión final del software se colocará en un Avnet ZedBoard (que tiene puertos para pantallas y consolas, pero no son necesarios ni deseados para el lanzamiento final).Entonces, durante el desarrollo, usaré printf para ver los mensajes de estado y de error, pero cuando termine, no quiero que se imprima nada.Esto es lo que he hecho:

versión.h

// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION


// Define which version we want to use
#define DEBUG_VERSION       // The current version
// #define RELEASE_VERSION  // To be uncommented when finished debugging

#ifndef __VERSION_H_      /* prevent circular inclusions */
    #define __VERSION_H_  /* by using protection macros */
    void board_init();
    void noprintf(const char *c, ...); // mimic the printf prototype
#endif

// Mimics the printf function prototype. This is what I'll actually 
// use to print stuff to the screen
void (* zprintf)(const char*, ...); 

// If debug version, use printf
#ifdef DEBUG_VERSION
    #include <stdio.h>
#endif

// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

#ifdef INVALID_VERSION
    // Won't allow compilation without a valid version define
    #error "Invalid version definition"
#endif

En version.c Definiré los 2 prototipos de funciones presentes en version.h

versión.c

#include "version.h"

/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return    None
*
*****************************************************************************/
void board_init()
{
    // Assign the print function to the correct function pointer
    #ifdef DEBUG_VERSION
        zprintf = &printf;
    #else
        // Defined below this function
        zprintf = &noprintf;
    #endif
}

/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return   None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
    return;
}

Observe cómo se crea el prototipo del puntero de función en version.h como

void (* zprintf)(const char *, ...);

Cuando se haga referencia a él en la aplicación, comenzará a ejecutarse hacia donde apunte, lo cual aún no se ha definido.

En version.c, aviso en el board_init()función donde zprintf se le asigna una función única (cuya firma de función coincide) dependiendo de la versión que se define en version.h

zprintf = &printf; zprintf llama a printf con fines de depuración

o

zprintf = &noprint; zprintf simplemente regresa y no ejecutará código innecesario

Ejecutar el código se verá así:

principalProg.c

#include "version.h"
#include <stdlib.h>
int main()
{
    // Must run board_init(), which assigns the function
    // pointer to an actual function
    board_init();

    void *ptr = malloc(100); // Allocate 100 bytes of memory
    // malloc returns NULL if unable to allocate the memory.

    if (ptr == NULL)
    {
        zprintf("Unable to allocate memory\n");
        return 1;
    }

    // Other things to do...
    return 0;
}

El código anterior utilizará printf si está en modo de depuración, o no hacer nada si está en modo de lanzamiento.Esto es mucho más fácil que revisar todo el proyecto y comentar o eliminar el código.Todo lo que necesito hacer es cambiar la versión en version.h ¡Y el código hará el resto!

El puntero de función generalmente se define por typedef, y se utiliza como parámetro y valor de retorno.

Las respuestas anteriores ya explican mucho, solo doy un ejemplo completo:

#include <stdio.h>

#define NUM_A 1
#define NUM_B 2

// define a function pointer type
typedef int (*two_num_operation)(int, int);

// an actual standalone function
static int sum(int a, int b) {
    return a + b;
}

// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
    return (*funp)(a, b);
}

// use function pointer as return value,
static two_num_operation get_sum_fun() {
    return &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // call function via pointer
    printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}

// test - use function pointer as param,
void test_pointer_as_param() {
    printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}

// test - use function pointer as return value,
void test_pointer_as_return_value() {
    printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}

int main() {
    test_pointer_as_variable();
    test_pointer_as_param();
    test_pointer_as_return_value();

    return 0;
}

Uno de los grandes usos de los punteros de función en C es llamar a una función seleccionada en tiempo de ejecución. Por ejemplo, la biblioteca de tiempo de ejecución C tiene dos rutinas, qsort y bsearch , que llevan un puntero a una función a la que se llama comparar dos elementos que se están ordenando; Esto le permite ordenar o buscar, respectivamente, cualquier cosa, en función de los criterios que desee utilizar.

Un ejemplo muy básico, si hay una función llamada print(int x, int y) que a su vez puede requerir llamar a una función (add() o sub(), que son del mismo tipo), entonces lo que haremos, nosotros agregará un argumento de puntero de función a la función print() como se muestra a continuación:

#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

El resultado es:

  El valor

es: 410
  el valor es: 390

Un puntero de función es una variable que contiene la dirección de una función.Dado que es una variable de puntero, aunque con algunas propiedades restringidas, puede usarla de manera muy similar a como lo haría con cualquier otra variable de puntero en estructuras de datos.

La única excepción que se me ocurre es tratar el puntero de función como si apuntara a algo distinto de un valor único.Hacer aritmética de punteros incrementando o disminuyendo un puntero de función o sumando/restando un desplazamiento a un puntero de función no es realmente de ninguna utilidad ya que un puntero de función solo apunta a una sola cosa, el punto de entrada de una función.

El tamaño de una variable de puntero de función, el número de bytes ocupados por la variable, puede variar según la arquitectura subyacente, p.x32 o x64 o lo que sea.

La declaración de una variable de puntero de función necesita especificar el mismo tipo de información que una declaración de función para que el compilador de C pueda realizar los tipos de comprobaciones que normalmente realiza.Si no especifica una lista de parámetros en la declaración/definición del puntero de función, el compilador de C no podrá verificar el uso de parámetros.Hay casos en los que esta falta de control puede resultar útil, pero recuerde que se ha eliminado una red de seguridad.

Algunos ejemplos:

int func (int a, char *pStr);    // declares a function

int (*pFunc)(int a, char *pStr);  // declares or defines a function pointer

int (*pFunc2) ();                 // declares or defines a function pointer, no parameter list specified.

int (*pFunc3) (void);             // declares or defines a function pointer, no arguments.

Las dos primeras declaraciones son algo similares en eso:

  • func es una función que toma un int y un char * y devuelve un int
  • pFunc es un puntero de función al que se le asigna la dirección de una función que toma una int y un char * y devuelve un int

Entonces, de lo anterior podríamos tener una línea fuente en la que la dirección de la función func() se asigna a la variable de puntero de función pFunc como en pFunc = func;.

Observe la sintaxis utilizada con una declaración/definición de puntero de función en la que se utilizan paréntesis para superar las reglas de precedencia de operadores naturales.

int *pfunc(int a, char *pStr);    // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr);  // declares a function pointer that returns an int

Varios ejemplos de uso diferentes

Algunos ejemplos de uso de un puntero de función:

int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variable
struct {                             // declare a struct that contains a function pointer
    int x22;
    int (*pFunc)(int a, char *pStr);
} thing = {0, func};                 // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr));  // declare a function pointer that points to a function that has a function pointer as an argument

Puede utilizar listas de parámetros de longitud variable en la definición de un puntero de función.

int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);

O no puede especificar ninguna lista de parámetros.Esto puede resultar útil, pero elimina la oportunidad de que el compilador de C realice comprobaciones en la lista de argumentos proporcionada.

int  sum ();      // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

Moldes estilo C

Puede utilizar conversiones de estilo C con punteros de función.Sin embargo, tenga en cuenta que un compilador de C puede ser poco estricto con las comprobaciones o proporcionar advertencias en lugar de errores.

int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum;               // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum;   // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum;     // compiler error of bad cast generated, parenthesis are required.

Comparar el puntero de función con la igualdad

Puede verificar que un puntero de función es igual a una dirección de función particular usando un if declaración, aunque no estoy seguro de cuán útil sería.Otros operadores de comparación parecerían tener incluso menos utilidad.

static int func1(int a, int b) {
    return a + b;
}

static int func2(int a, int b, char *c) {
    return c[0] + a + b;
}

static int func3(int a, int b, char *x) {
    return a + b;
}

static char *func4(int a, int b, char *c, int (*p)())
{
    if (p == func1) {
        p(a, b);
    }
    else if (p == func2) {
        p(a, b, c);      // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
    } else if (p == func3) {
        p(a, b, c);
    }
    return c;
}

Una variedad de punteros de función

Y si desea tener una matriz de punteros de función, cada uno de los elementos de los cuales la lista de argumentos tiene diferencias, entonces puede definir un puntero de función con la lista de argumentos sin especificar (no void lo que significa que no hay argumentos pero simplemente no especificado) algo como lo siguiente, aunque es posible que vea advertencias del compilador de C.Esto también funciona para un parámetro de puntero de función a una función:

int(*p[])() = {       // an array of function pointers
    func1, func2, func3
};
int(**pp)();          // a pointer to a function pointer


p[0](a, b);
p[1](a, b, 0);
p[2](a, b);      // oops, left off the last argument but it compiles anyway.

func4(a, b, 0, func1);
func4(a, b, 0, func2);  // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);

    // iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
    func4(a, b, 0, p[i]);
}
    // iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
    (*pp)(a, b, 0);          // pointer to a function pointer so must dereference it.
    func4(a, b, 0, *pp);     // pointer to a function pointer so must dereference it.
}

estilo C namespace Usando Global struct con punteros de función

Puedes usar el static palabra clave para especificar una función cuyo nombre es el alcance del archivo y luego asignar esto a una variable global como una forma de proporcionar algo similar a namespace funcionalidad de C++.

En un archivo de encabezado defina una estructura que será nuestro espacio de nombres junto con una variable global que lo use.

typedef struct {
   int (*func1) (int a, int b);             // pointer to function that returns an int
   char *(*func2) (int a, int b, char *c);  // pointer to function that returns a pointer
} FuncThings;

extern const FuncThings FuncThingsGlobal;

Luego en el archivo fuente C:

#include "header.h"

// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
    return a + b;
}

static char *func2 (int a, int b, char *c)
{
    c[0] = a % 100; c[1] = b % 50;
    return c;
}

const FuncThings FuncThingsGlobal = {func1, func2};

Luego, esto se usaría especificando el nombre completo de la variable de estructura global y el nombre del miembro para acceder a la función.El const El modificador se utiliza a nivel global para que no se pueda cambiar por accidente.

int abcd = FuncThingsGlobal.func1 (a, b);

Áreas de aplicación de los punteros de función

Un componente de biblioteca DLL podría hacer algo similar al estilo C namespace Enfoque en el que se solicita una interfaz de biblioteca particular desde un método de fábrica en una interfaz de biblioteca que admite la creación de un struct que contiene punteros de función.Esta interfaz de biblioteca carga la versión de DLL solicitada, crea una estructura con los punteros de función necesarios y luego devuelve la estructura a la persona que llama para su uso.

typedef struct {
    HMODULE  hModule;
    int (*Func1)();
    int (*Func2)();
    int(*Func3)(int a, int b);
} LibraryFuncStruct;

int  LoadLibraryFunc LPCTSTR  dllFileName, LibraryFuncStruct *pStruct)
{
    int  retStatus = 0;   // default is an error detected

    pStruct->hModule = LoadLibrary (dllFileName);
    if (pStruct->hModule) {
        pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
        pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
        pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
        retStatus = 1;
    }

    return retStatus;
}

void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
    if (pStruct->hModule) FreeLibrary (pStruct->hModule);
    pStruct->hModule = 0;
}

y esto podría usarse como en:

LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
//  ....
myLib.Func1();
//  ....
FreeLibraryFunc (&myLib);

Se puede utilizar el mismo enfoque para definir una capa de hardware abstracta para código que utiliza un modelo particular del hardware subyacente.Una fábrica completa los punteros de función con funciones específicas del hardware para proporcionar la funcionalidad específica del hardware que implementa las funciones especificadas en el modelo de hardware abstracto.Esto se puede usar para proporcionar una capa de hardware abstracta utilizada por el software que llama a una función de fábrica para obtener la interfaz de función de hardware específica y luego usa los punteros de función proporcionados para realizar acciones para el hardware subyacente sin necesidad de conocer los detalles de implementación sobre el objetivo específico. .

Punteros de funciones para crear delegados, controladores y devoluciones de llamadas

Puede utilizar punteros de función como una forma de delegar alguna tarea o funcionalidad.El ejemplo clásico en C es el puntero de función de delegado de comparación utilizado con las funciones de la biblioteca estándar de C. qsort() y bsearch() para proporcionar el orden de clasificación para ordenar una lista de elementos o realizar una búsqueda binaria en una lista ordenada de elementos.El delegado de la función de comparación especifica el algoritmo de intercalación utilizado en la clasificación o la búsqueda binaria.

Otro uso es similar a aplicar un algoritmo a un contenedor de biblioteca de plantillas estándar de C++.

void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for ( ; pList < pListEnd; pList += sizeItem) {
        p (pList);
    }

    return pArray;
}

int pIncrement(int *pI) {
    (*pI)++;

    return 1;
}

void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for (; pList < pListEnd; pList += sizeItem) {
        p(pList, pResult);
    }

    return pArray;
}

int pSummation(int *pI, int *pSum) {
    (*pSum) += *pI;

    return 1;
}

// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;

ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);

Otro ejemplo es el código fuente de la GUI en el que se registra un controlador para un evento en particular proporcionando un puntero de función que realmente se llama cuando ocurre el evento.El marco Microsoft MFC con sus mapas de mensajes utiliza algo similar para manejar los mensajes de Windows que se entregan a una ventana o hilo.

Las funciones asincrónicas que requieren una devolución de llamada son similares a un controlador de eventos.El usuario de la función asincrónica llama a la función asincrónica para iniciar alguna acción y proporciona un puntero de función que la función asincrónica llamará una vez que se complete la acción.En este caso, el evento es la función asincrónica que completa su tarea.

Comenzando desde cero, la función tiene alguna dirección de memoria desde donde comienzan a ejecutarse. En lenguaje ensamblador se les llama como (llame a & Quot; dirección de memoria de la función & Quot;). Ahora regrese a C Si la función tiene una dirección de memoria, entonces pueden ser manipulados por punteros en C. Por lo tanto, según las reglas de C

1.Primero necesita declarar un puntero para funcionar 2.Pasar la dirección de la función deseada

**** Nota - > las funciones deben ser del mismo tipo ****

Este programa simple ilustrará cada cosa.

#include<stdio.h>
void (*print)() ;//Declare a  Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
                //The Functions should Be of Same Type
int main()
{
 print=sayhello;//Addressof sayhello is assigned to print
 print();//print Does A call To The Function 
 return 0;
}

void sayhello()
{
 printf("\n Hello World");
}

ingrese la descripción de la imagen aquí Después de eso, veamos cómo la máquina los comprende. Vistazo de las instrucciones de la máquina del programa anterior en arquitectura de 32 bits.

El área de la marca roja muestra cómo se intercambia la dirección y cómo se almacena en eax. Entonces hay una instrucción de llamada en eax. eax contiene la dirección deseada de la función.

Dado que los punteros de función a menudo son devoluciones de llamada escritas, es posible que desee echar un vistazo a escriba devoluciones de llamada seguras . Lo mismo se aplica a los puntos de entrada, etc. de funciones que no son devoluciones de llamada.

C es bastante voluble y tolerante al mismo tiempo :)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top