parametros - punteros en c




¿Cómo funcionan los punteros de función en C? (8)

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 int :

int (*functionPtr)(int,int);

Ahora podemos apuntar con seguridad a nuestra función:

functionPtr = &addInt;

Ahora que tenemos un puntero a la función, vamos a usarlo:

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 los punteros de función en los valores de retorno (intenta mantenerte, se ensucia):

// 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 typedef :

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;
}

He tenido alguna experiencia últimamente con punteros de función en C.

Así que continuando con la tradición de responder a sus propias preguntas, decidí hacer un pequeño resumen de los conceptos básicos, para aquellos que necesitan una introducción rápida al tema.


Otro buen uso para punteros de función:
Cambio entre versiones sin dolor.

Son muy útiles para usar cuando se desean 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 / deseados para el lanzamiento final). Así que durante el desarrollo, utilizaré 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:

version.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 los prototipos de 2 funciones presentes en version.h

version.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 el puntero a la función está prototipado en version.h como

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

Cuando se hace referencia en la aplicación, comenzará a ejecutarse dondequiera que esté apuntando, que aún no se ha definido.

En version.c , observe en la función board_init() donde a zprintf se le asigna una función única (cuya función coincide con la función) dependiendo de la versión definida en version.h

zprintf = &printf; zprintf llama a printf para propósitos de depuración

o

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

Ejecutar el código se verá así:

mainProg.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 usará printf si está en modo de depuración, o no hace nada si está en modo de liberación. Esto es mucho más fácil que pasar por 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!


La función de inicio desde cero tiene alguna dirección de memoria desde donde comienzan a ejecutarse. En lenguaje ensamblador, se llaman como (llamar "dirección de memoria de la función"). Ahora vuelva a C Si la función tiene una dirección de memoria, los punteros pueden manipularlos en C. Por lo tanto, según las reglas de C

1. En primer lugar, debe declarar un puntero para que funcione. 2. Pase 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");
}

Después de eso, veamos cómo la máquina los entiende. Un máximo de instrucciones de la máquina del programa anterior en una arquitectura de 32 bits.

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


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 una instrucción de x86 ret .

Normalmente no escribirías esto a mano, escribirías en lenguaje ensamblador y luego nasm un ensamblador como nasm para ensamblarlo en un binario plano que hexadumpes 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. Escribe un contador for-loop a 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 cuenta 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 literales de cadena en la sección .rodata (o .rdata en Windows), que se vincula como parte del segmento de texto (junto con el código para las funciones).

El segmento de texto tiene permiso de lectura y ejecución, por lo que convertir los literales de cadena en punteros de función funciona sin necesidad de llamadas al sistema mprotect() o VirtualProtect() como si fuera necesario para la memoria asignada dinámicamente. (O gcc -z execstack vincula el programa con pila + segmento de datos + ejecutable del montón, como un hack rápido).

Para desmontarlos, 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 desensamblando con objdump -D -rwC -Mintel , podemos obtener el ensamblaje y descubrir que este código viola el ABI al anexar EBX (un registro de llamadas preservadas) y es generalmente 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 llamada predeterminadas en todos esos sistemas operativos pasan argumentos en la pila en lugar de hacerlo de forma más eficiente en los registros. Sin embargo, EBX se conserva en todas las convenciones de llamadas normales, por lo que usarlo como un registro inicial sin guardarlo / restaurarlo puede hacer que la persona que llama se bloquee fácilmente.


Un puntero de función es una variable que contiene la dirección de una función. Ya 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 apuntando a algo que no sea un solo valor. Hacer aritmética de punteros incrementando o disminuyendo un puntero de función o agregando / restando un desplazamiento a un puntero de función no es realmente de ninguna utilidad, ya que el 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, por ejemplo, x32 o x64 o lo que sea.

La declaración para 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 realice las 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 que esta falta de verificación puede ser ú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 que:

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

Entonces, de lo anterior podríamos tener una línea de origen 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 del operador natural.

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 diferente

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 usar 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 una lista de parámetros en absoluto. Esto puede ser útil pero elimina la oportunidad para que el compilador de C realice verificaciones en la lista de argumentos provista.

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

Puedes usar moldes de estilo C con punteros de función. Sin embargo, tenga en cuenta que un compilador de C puede ser poco estricto con respecto a 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.

Compare la función de puntero a la igualdad

Puede verificar que un puntero de función es igual a una dirección de función particular usando una instrucción if , aunque no estoy seguro de cuán útil sería. Otros operadores de comparación parecen 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 matriz 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, puede definir un puntero de función con la lista de argumentos no especificada (no lo voidque significa que no hay argumentos sino solo no especificada) algo como lo siguiente Puede ver advertencias del compilador de C. Esto también funciona para un parámetro de puntero de función para 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 namespaceUsando Global structcon Punteros de Función

Puede usar la staticpalabra clave para especificar una función cuyo nombre es el alcance del archivo y luego asignarlo a una variable global como una forma de proporcionar algo similar a la namespacefuncionalidad 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};

Esto se usaría luego especificando el nombre completo de la variable de estructura global y el nombre del miembro para acceder a la función. El constmodificador se utiliza en el global para que no se pueda cambiar por accidente.

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

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

Un componente de biblioteca DLL podría hacer algo similar al namespaceenfoque de estilo C en el que se solicita una interfaz de biblioteca particular a un método de fábrica en una interfaz de biblioteca que admite la creación de una structfunción que contiene punteros. una estructura con los punteros de función necesarios, y luego devuelve la estructura al llamante solicitante 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 ser usado como en:

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

El mismo enfoque se puede usar para definir una capa de hardware abstracta para el código que usa un modelo particular del hardware subyacente. Los punteros a funciones se completan con funciones específicas de hardware por una fábrica para proporcionar la funcionalidad específica de hardware que implementa 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 utiliza 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 función para crear delegados, manejadores y devoluciones de llamada

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

Otro uso es similar a la aplicación de un algoritmo a un contenedor de la 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 con el código fuente de la GUI en el que se registra un controlador para un evento en particular al proporcionar un puntero a la función al que realmente se llama cuando ocurre el evento. El marco de Microsoft MFC con sus mapas de mensajes usa algo similar para manejar los mensajes de Windows que se envían a una ventana o hilo.

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


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 de C tiene dos rutinas, qsort y bsearch, que llevan un puntero a una función que se llama para comparar dos elementos que están siendo ordenados; esto le permite ordenar o buscar, respectivamente, cualquier cosa, según los criterios que desee utilizar.

Un ejemplo muy básico, si hay una función llamada imprimir (int x, int y) que a su vez puede requerir llamar a add () function o sub () que son de tipos similares, entonces lo que haremos, agregaremos una función Argumento de puntero a la función print () como se muestra a continuación:

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

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

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

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

    return 0;
}

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 operador es un regalo de muerte, pero parece implicar que estamos configurando el texto de alguna clase de String como "hello" .

Mediante el uso de punteros de función, es posible emular métodos en C.

¿Cómo se logra esto?

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

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 puede verse, los métodos de la clase String son en realidad punteros a la función declarada. Al preparar la instancia de String , se newString función newString para configurar los punteros de función a sus funciones respectivas:

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, la función getString que se invoca al invocar el método get 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 hay un concepto de una instancia de un objeto y que tenga métodos que sean realmente parte de un objeto, por lo que se debe pasar un "objeto propio" en cada invocación. (Y lo internal es solo una struct oculta que se omitió en la lista de códigos anterior - es una forma de ocultar información, pero eso no es relevante para los punteros de función).

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

Con esa pequeña explicación que tiene que pasar una referencia a ti mismo fuera del camino, pasaremos a la siguiente parte, que es la herencia en C.

Digamos que queremos hacer una subclase de String , digamos ImmutableString . Para hacer que la cadena sea inmutable, no se podrá acceder al método de set , mientras se mantiene el acceso para get y la 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, una vez más, punteros de función. Esta vez, la declaración para el método set no está presente, por lo tanto, no se puede llamar en un ImmutableString .

En cuanto a la implementación de ImmutableString , el único código relevante es la función "constructor", 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 crear una instancia de ImmutableString , los punteros de función para los métodos de get y length refieren al método String.get y String.length , pasando por la variable base que es un objeto String almacenado internamente.

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

Podemos continuar con el polimorfismo en C.

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

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

La adición de un método de anulación de length en ImmutableString se puede realizar agregando un método de length lengthOverrideMethod :

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

Luego, el puntero de función para el método de length en el constructor se lengthOverrideMethod 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, en lugar de tener un comportamiento idéntico para el método de length en la clase ImmutableString como clase String , ahora el método de length se referirá al comportamiento definido en la función lengthOverrideMethod .

Debo agregar un descargo de responsabilidad de que todavía estoy aprendiendo a escribir con un estilo de programación orientado a objetos en C, por lo que probablemente hay puntos que no expliqué bien, o tal vez no estoy del todo claro en cuanto a la mejor manera de implementar OOP en C. Pero mi propósito era tratar de ilustrar uno de los muchos usos de los punteros de función.

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


Los punteros de función son útiles en muchas situaciones, por ejemplo:

  • Los miembros de los objetos COM son punteros a la función ag: This->lpVtbl->AddRef(This);AddRef es un puntero a una función.
  • función de devolución de llamada, por ejemplo, una función definida por el usuario para comparar dos variables a pasar como una devolución de llamada a una función de clasificación especial
  • Muy útil para la implementación del plugin y la aplicación SDK.




function-pointers