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

10 minutos de lectura

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

Entonces, siguiendo con la tradición de responder sus 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.

  • Además: para un análisis un poco más profundo de los punteros C, consulte blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge. También, Programación desde cero muestra cómo funcionan a nivel de máquina. Comprender el “modelo de memoria” de C es muy útil para comprender cómo funcionan los punteros de C.

    – Abbafei

    22 de mayo de 2013 a las 11:17

  • Gran información. Sin embargo, por el título, hubiera esperado ver realmente una explicación de cómo “funcionan los punteros de función”, no cómo están codificados 🙂

    – Bogdan Alexandru

    28 de agosto de 2014 a las 12:39

  • La siguiente respuesta es más corta y mucho más fácil de entender: stackoverflow.com/a/142809/2188550

    usuario2188550

    08/01/2021 a las 22:31

Los punteros de función en C se pueden usar 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 muerto, pero seguro parece implicar que estamos configurando el texto de algunos String clase para ser "hello".

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

¿Cómo se logra esto?

Él String la clase es en realidad un 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 puede verse, los métodos de String class son en realidad punteros de función a la función declarada. Al preparar la instancia de la Stringla 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 tener métodos que en realidad son 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ó de 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");se debe pasar el objeto para realizar la acción en s1->set(s1, "hello").

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

Digamos que queremos hacer una subclase de Stringdi un ImmutableString. Para hacer la cadena inmutable, el set el método no será accesible, mientras se mantiene el acceso a get y lengthy forzar 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 de la set método no está presente, por lo tanto, no se puede llamar en un ImmutableString.

En cuanto a la implementación de la ImmutableStringel único código relevante es la función “constructor”, el 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 ImmutableStringla 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 tendría que hacerse es:

  1. Agregue una función que va a servir como anulación length método.
  2. Vaya al “constructor” y establezca el puntero de función en la anulación length método.

Agregar una anulación length método en ImmutableString puede realizarse agregando 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, en lugar de tener un comportamiento idéntico para el length método en ImmutableString clase como la String clase, ahora la length 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 orientado a objetos en C, por lo que probablemente haya puntos que no expliqué bien, o que pueden estar fuera de lugar en términos de 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:

  • ¿Orientación a objetos en C?
  • ¿Puedes escribir código orientado a objetos en C?

  • ¡Esta respuesta es horrible! No solo implica que OO de alguna manera depende de la notación de puntos, ¡sino que también fomenta poner basura en sus objetos!

    – Alexéi Averchenko

    16/09/2012 a las 14:30

  • Esto es OO, está bien, pero no se acerca al OO de estilo C. Lo que ha implementado de manera deficiente es OO basado en prototipos de estilo Javascript. Para obtener OO al estilo de C++/Pascal, necesitaría: 1. Tener una estructura const para una tabla virtual de cada clase con miembros virtuales. 2. Tener puntero a esa estructura en objetos polimórficos. 3. Llame a los métodos virtuales a través de la tabla virtual y a todos los demás métodos directamente, generalmente siguiendo algunos ClassName_methodName convención de nomenclatura de funciones. Solo entonces obtiene el mismo tiempo de ejecución y costos de almacenamiento que en C++ y Pascal.

    – Kuba no se ha olvidado de Mónica

    18/03/2013 a las 21:53


  • Trabajar OO con un lenguaje que no pretende ser OO siempre es una mala idea. Si quiere OO y todavía tiene C, simplemente trabaje con C++.

    – rbaleksandar

    4 de julio de 2013 a las 15:21

  • @rbaleksandar Dile eso a los desarrolladores del kernel de Linux. “siempre es una mala idea” Es estrictamente tu opinión, con la que discrepo rotundamente.

    – Jonathan Reinhart

    30 de abril de 2015 a las 12:31

  • Me gusta esta respuesta pero no emita malloc

    – gato

    29/09/2016 a las 14:41

  • Nota: esto no funciona si la prevención de ejecución de datos está habilitada (por ejemplo, en Windows XP SP2+), porque las cadenas C normalmente no se marcan como ejecutables.

    – SecurityMatt

    12 de febrero de 2013 a las 5:53

  • ¡Hola Matt! Dependiendo del nivel de optimización, GCC a menudo incluirá constantes de cadena en línea en el segmento de TEXTO, por lo que esto funcionará incluso en la versión más nueva de Windows, siempre que no rechace este tipo de optimización. (IIRC, la versión de MINGW en el momento de mi publicación hace más de dos años literales de cadena en línea en el nivel de optimización predeterminado)

    – Lee

    2 de enero de 2014 a las 6:20

  • ¿Podría alguien explicar qué está pasando aquí? ¿Qué son esos literales de cadena de aspecto extraño?

    – ajay

    20 de enero de 2014 a las 10:17


  • @ajay Parece que está escribiendo valores hexadecimales sin procesar (por ejemplo, ‘\ x00’ es lo mismo que ‘/ 0’, ambos son iguales a 0) en una cadena, luego convierte la cadena en un puntero de función C y luego ejecuta el puntero de función C porque es el diablo.

    – ejk314

    21 de febrero de 2014 a las 21:27


  • hola FUZxxl, creo que puede variar según el compilador y la versión del sistema operativo. El código anterior parece funcionar bien en codepad.org; codepad.org/FMSDQ3ME

    – Lee

    13 de marzo de 2014 a las 0:48


La función Comenzar desde cero tiene alguna dirección de memoria desde donde comienzan a ejecutarse. En lenguaje ensamblador, se llaman como (llame “dirección de memoria de la función”). 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 debe declarar un puntero a la función 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");
}

ingrese la descripción de la imagen aquíDespués de eso, veamos cómo la máquina los entiende. Un vistazo a las 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 se almacena la dirección en eax. Entonces hay una instrucción de llamada en eax. eax contiene la dirección deseada de la función.

  • ¿Cómo uso un puntero de función devuelto por un método? something() parece simplemente bloquear el programa. Tengo algo de contexto y código fallido aquí: stackoverflow.com/questions/67152106

    – Aarón Franke

    18 abr 2021 a las 18:41

Uno de mis usos favoritos para los punteros de función es como iteradores baratos 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);
}

  • ¿Cómo uso un puntero de función devuelto por un método? something() parece simplemente bloquear el programa. Tengo algo de contexto y código fallido aquí: stackoverflow.com/questions/67152106

    – Aarón Franke

    18 abr 2021 a las 18:41

Dado que los punteros de función a menudo se escriben devoluciones de llamada, 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 e indulgente al mismo tiempo 🙂

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad