2009-10-19 24 views
175

Siempre he estado un poco perplejo cuando leo el código de otras personas que tiene typedefs para punteros a funciones con argumentos. Recuerdo que me llevó un tiempo llegar a esa definición al tratar de entender un algoritmo numérico escrito en C hace un tiempo. Entonces, ¿podría compartir sus consejos y opiniones sobre cómo escribir buenos typedefs para punteros a funciones (Do's y Do's), en cuanto a por qué son útiles y cómo entender el trabajo de los demás? ¡Gracias!Entendiendo typedefs para punteros de funciones en C

+1

¿Puede dar algunos ejemplos? – Artelius

+2

¿No quiere decir typedefs para punteros a función, en lugar de macros para punteros de función? He visto lo primero pero no lo último. – dave4420

+0

Consulte también [Cómo declarar un puntero a la función __stdcall] (http://stackoverflow.com/q/5298394). – jww

Respuesta

21

cdecl es una gran herramienta para descifrar la sintaxis extraña, como declaraciones de puntero a la función. Puedes usarlo para generarlos también.

En cuanto a consejos para hacer declaraciones complicadas más fáciles de analizar para mantenimiento futuro (por usted o por otros), recomiendo hacer typedef s de trozos pequeños y usar esas piezas pequeñas como bloques para expresiones más grandes y complicadas. Por ejemplo:

typedef int (*FUNC_TYPE_1)(void); 
typedef double (*FUNC_TYPE_2)(void); 
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2); 

en lugar de:

typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void); 

cdecl pueden ayudar a cabo con estas cosas:

cdecl> explain int (*FUNC_TYPE_1)(void) 
declare FUNC_TYPE_1 as pointer to function (void) returning int 
cdecl> explain double (*FUNC_TYPE_2)(void) 
declare FUNC_TYPE_2 as pointer to function (void) returning double 
cdecl> declare FUNC_TYPE_3 as pointer to function (pointer to function (void) returning double) returning pointer to function (void) returning int 
int (*(*FUNC_TYPE_3)(double (*)(void)))(void) 

Y es (de hecho) exactamente cómo me genera ese lío loco por encima de .

+2

Hola Carl, ese fue un ejemplo y una explicación muy perspicaces. Además, gracias por mostrar el uso de cdecl. Muy apreciado. –

+0

¿Hay cdecl para Windows? – Jack

+0

@Jack, estoy seguro de que puedes construirlo, sí. –

54

Un puntero a la función es como cualquier otro puntero, pero apunta a la dirección de una función en lugar de a la dirección de los datos (en el montón o la pila). Al igual que cualquier puntero, debe escribirse correctamente. Las funciones se definen por su valor de retorno y los tipos de parámetros que aceptan. Entonces, para describir completamente una función, debe incluir su valor de retorno y el tipo de cada parámetro es aceptado. Cuando escribe definición, le da un 'nombre descriptivo' que facilita la creación y referencia de punteros usando esa definición.

Así, por ejemplo, suponga que tiene una función:

float doMultiplication (float num1, float num2) { 
    return num1 * num2; } 

entonces la siguiente typedef:

typedef float(*pt2Func)(float, float); 

se pueden utilizar para apuntar a esta función doMulitplication. Simplemente define un puntero a una función que devuelve un flotante y toma dos parámetros, cada uno de tipo float. Esta definición tiene el nombre descriptivo pt2Func. Tenga en cuenta que pt2Func puede apuntar a CUALQUIER función que devuelva un flotador y tome 2 flotantes.

para que pueda crear un puntero que apunta a la función doMultiplication de la siguiente manera:

pt2Func *myFnPtr = &doMultiplication; 

y se puede invocar la función usando este puntero de la siguiente manera:

float result = (*myFnPtr)(2.0, 5.1); 

Esto hace que la buena lectura: http://www.newty.de/fpt/index.html

+0

psychotik, gracias! Eso fue útil. El enlace a la página web de punteros a la función es muy útil. Leyéndolo ahora –

+0

... Sin embargo, ese enlace newty.de no parece hablar sobre typedefs en absoluto :( Así que, aunque ese enlace es genial, pero las respuestas en este hilo sobre typedefs son invaluables! –

+6

Es posible que desee hacer ' pt2Func myFnPtr = & doMultiplication; 'en lugar de' pt2Func * myFnPtr = & doMultiplication; 'como' myFnPtr' ya es un puntero – Tamilselvan

238

Considere la función signal() del estándar C:

extern void (*signal(int, void(*)(int)))(int); 

perfectamente oscuramente obvia - es una función que toma dos argumentos, un entero y un puntero a una función que toma un entero como argumento y devuelve nada, y (signal()) devuelve un puntero a una función que toma un entero como argumento y no devuelve nada.

si escribe:

typedef void (*SignalHandler)(int signum); 

continuación, en su lugar puede declarar signal() como:

extern SignalHandler signal(int signum, SignalHandler handler); 

Esto significa la misma cosa, pero por lo general se considera como algo más fácil de leer. Es más claro que la función toma un int y un SignalHandler y devuelve un SignalHandler.

Sin embargo, lleva un tiempo acostumbrarse. Sin embargo, lo único que no puede hacer es escribir una función de controlador de señal usando SignalHandlertypedef en la definición de la función.

sigo siendo de la vieja escuela que prefiere invocar un puntero de función como:

(*functionpointer)(arg1, arg2, ...); 

sintaxis moderna utiliza simplemente:

functionpointer(arg1, arg2, ...); 

puedo ver por qué esto funciona - acabo Prefiero saber que necesito buscar dónde se inicializa la variable en lugar de una función llamada functionpointer.


Sam comentó:

he visto esta explicación antes. Y entonces, como es el caso ahora, creo que lo que no he tenido era la conexión entre las dos afirmaciones:

extern void (*signal(int, void()(int)))(int); /*and*/ 

    typedef void (*SignalHandler)(int signum); 
    extern SignalHandler signal(int signum, SignalHandler handler); 

O, lo que quiero preguntar es, ¿cuál es el concepto subyacente que se puede utilizar para llegar a la segunda versión que tienes? ¿Cuál es el elemento fundamental que conecta "SignalHandler" y el primer typedef? Creo que lo que debe explicarse aquí es qué está haciendo typedef aquí.

Probemos de nuevo. El primero de ellos se saca directamente del estándar C: lo reescribí y verifiqué que tenía el paréntesis correcto (no hasta que lo corregí, es una cookie difícil de recordar).

Antes que nada, recuerde que typedef introduce un alias para un tipo. Por lo tanto, el alias es SignalHandler y su tipo es:

puntero a una función que toma un entero como argumento y no devuelve nada.

La parte 'devoluciones nada' se deletrea void; el argumento que es un número entero es (confío) autoexplicativo. La siguiente notación es simplemente (o no) la forma en C hechizos puntero a funcionar toma de argumentos como se especifica y devolver el tipo dado:

type (*function)(argtypes); 

Después de crear el tipo de controlador de señal, lo puedo usar para declarar variables y así sucesivamente. Por ejemplo:

static void alarm_catcher(int signum) 
{ 
    fprintf(stderr, "%s() called (%d)\n", __func__, signum); 
} 

static void signal_catcher(int signum) 
{ 
    fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum); 
    exit(1); 
} 

static struct Handlers 
{ 
    int    signum; 
    SignalHandler handler; 
} handler[] = 
{ 
    { SIGALRM, alarm_catcher }, 
    { SIGINT, signal_catcher }, 
    { SIGQUIT, signal_catcher }, 
}; 

int main(void) 
{ 
    size_t num_handlers = sizeof(handler)/sizeof(handler[0]); 
    size_t i; 

    for (i = 0; i < num_handlers; i++) 
    { 
     SignalHandler old_handler = signal(handler[i].signum, SIG_IGN); 
     if (old_handler != SIG_IGN) 
      old_handler = signal(handler[i].signum, handler[i].handler); 
     assert(old_handler == SIG_IGN); 
    } 

    ...continue with ordinary processing... 

    return(EXIT_SUCCESS); 
} 

Tenga en cuenta How to avoid using printf() in a signal handler?

Por lo tanto, lo que hemos hecho aquí - aparte de Omitir 4 cabeceras estándar que serían necesarios para hacer el código compilar limpiamente?

Las primeras dos funciones son funciones que toman un solo entero y no devuelven nada. Uno de ellos realmente no regresa en absoluto gracias al exit(1);, pero el otro regresa después de imprimir un mensaje. Tenga en cuenta que el estándar C no le permite hacer mucho dentro de un manejador de señal; POSIX es un poco más generoso en lo que está permitido, pero oficialmente no sanciona llamando al fprintf(). También imprimo el número de señal que se recibió. En la función alarm_handler(), el valor siempre será SIGALRM ya que es la única señal para la que es controlador, pero signal_handler() puede obtener SIGINT o SIGQUIT como el número de señal porque la misma función se usa para ambos.

Luego creo una matriz de estructuras, donde cada elemento identifica un número de señal y el controlador que se instalará para esa señal. Elegí preocuparme por 3 señales; A menudo me preocupa SIGHUP, SIGPIPE y SIGTERM y si están definidos (compilación condicional #ifdef), pero eso solo complica las cosas. Probablemente también use POSIX sigaction() en lugar de signal(), pero ese es otro problema; sigamos con lo que comenzamos.

La función main() repite la lista de manipuladores que se instalarán. Para cada controlador, primero llama al signal() para averiguar si el proceso actualmente ignora la señal y, al hacerlo, instala SIG_IGN como controlador, lo que garantiza que la señal permanece ignorada. Si la señal no se ignoraba previamente, llama de nuevo al signal(), esta vez para instalar el controlador de señal preferido. (El otro valor es supuestamente , el manejador de señal predeterminado para la señal). Debido a que la primera llamada a 'signal()' establece el manejador a SIG_IGN y signal() devuelve el manejador de error anterior, el valor de old debe ser ifSIG_IGN - de ahí la afirmación. (Bueno, podría ser SIG_ERR si algo salió radicalmente mal, pero luego lo sabré de la activación de la afirmación.)

El programa hace sus cosas y sale normalmente.

Tenga en cuenta que el nombre de una función se puede considerar como un puntero a una función del tipo apropiado. Cuando no aplica la función, llame paréntesis, como en los inicializadores, por ejemplo, el nombre de la función se convierte en un puntero de función. Esta es también la razón por la cual es razonable invocar funciones a través de la notación pointertofunction(arg1, arg2); cuando vea alarm_handler(1), puede considerar que alarm_handler es un puntero a la función y, por lo tanto, alarm_handler(1) es una invocación de una función a través de un puntero de función.

Hasta ahora, he demostrado que una variable SignalHandler es relativamente fácil de usar, siempre que tenga el tipo de valor adecuado para asignarle, que es lo que manejan las dos funciones del manejador de señal proporcionar.

Ahora volvemos a la pregunta: ¿cómo se relacionan las dos declaraciones para signal() entre sí.

Repasemos la segunda declaración:

extern SignalHandler signal(int signum, SignalHandler handler); 

Si cambiamos el nombre de la función y el tipo de esta manera:

extern double function(int num1, double num2); 

usted no tendría ningún problema interpretar esto como una función que toma un int y double como argumentos y devuelve un valor de double (¿lo haría? tal vez sería mejor no 'confesar si eso es problemático - pero tal vez debería tener cuidado al hacer preguntas tan difíciles como esta si es un problema).

Ahora, en lugar de ser un double, la función signal() toma un SignalHandler como segundo argumento, y devuelve uno como resultado.

La mecánica mediante el cual que también puede ser entendido como:

extern void (*signal(int signum, void(*handler)(int signum)))(int signum); 

son difíciles de explicar - así que probablemente voy a meter la pata. Esta vez he dado los nombres de los parámetros, aunque los nombres no son críticos.

En general, en C, el mecanismo de declaración es tal que si se escribe:

type var; 

entonces cuando se escribe var que representa un valor determinado de la type. Por ejemplo:

int  i;   // i is an int 
int *ip;   // *ip is an int, so ip is a pointer to an integer 
int  abs(int val); // abs(-1) is an int, so abs is a (pointer to a) 
         // function returning an int and taking an int argument 

En el estándar, typedef se trata como una clase de almacenamiento en la gramática, más bien como static y extern son clases de almacenamiento.

typedef void (*SignalHandler)(int signum); 

significa que cuando vea una variable de tipo SignalHandler (digamos alarm_handler) invocada como:

(*alarm_handler)(-1); 

el resultado tiene type void - no hay ningún resultado. Y (*alarm_handler)(-1); es una invocación de alarm_handler() con el argumento -1.

lo tanto, si declaramos:

extern SignalHandler alt_signal(void); 

significa que:

(*alt_signal)(); 

representa un valor nulo. Y por lo tanto:

extern void (*alt_signal(void))(int signum); 

es equivalente. Ahora, signal() es más complejo, ya que no sólo devuelve un SignalHandler, también acepta tanto un int y un SignalHandler como argumentos:

extern void (*signal(int signum, SignalHandler handler))(int signum); 

extern void (*signal(int signum, void (*handler)(int signum)))(int signum); 

Si aún así se confunde, no estoy seguro de cómo ayudar - es aún en algunos niveles me resulta misterioso, pero me he acostumbrado a cómo funciona y, por lo tanto, puedo decirte que si te quedas con él durante otros 25 años más o menos, se convertirá en una segunda naturaleza para ti (y tal vez incluso un poco más rápido). si eres inteligente).

+19

¡Pensé que sabía leer casi cualquier código C pero esta función de señal me dio problemas! Buena respuesta. – toto

+1

He visto esta explicación antes. Y luego, como es el caso ahora, Creo que lo que no obtuve fue la conexión entre las dos declaraciones: extern void (* signal (int, void (*) (int))) (int);/* y */ typedef void (* SignalHandler) (int signum); señal Extern SignalHandler (int signum, controlador SignalHandler); O, lo que quiero preguntar es, ¿cuál es el concepto subyacente que uno puede utilizar para llegar a la segunda versión que tiene? ¿Cuál es el elemento fundamental que conecta "SignalHandler" y el primer typedef? Creo que lo que debe explicarse aquí es qué está haciendo typedef aquí. Thx –

+0

http://en.wikipedia.org/wiki/Typedef – psychotik

22

Una forma muy fácil de entender typedef de la función de puntero:

int add(int a, int b) 
{ 
    return (a+b); 
} 

typedef int (*add_integer)(int, int); //declaration of function pointer 

int main() 
{ 
    add_integer addition = add; //typedef assigns a new variable i.e. "addition" to original function "add" 
    int c = addition(11, 11); //calling function via new variable 
    printf("%d",c); 
    return 0; 
} 
10
int add(int a, int b) 
{ 
    return (a+b); 
} 
int minus(int a, int b) 
{ 
    return (a-b); 
} 

typedef int (*math_func)(int, int); //declaration of function pointer 

int main() 
{ 
    math_func addition = add; //typedef assigns a new variable i.e. "addition" to original function "add" 
    math_func substract = minus; //typedef assigns a new variable i.e. "substract" to original function "minus" 

    int c = addition(11, 11); //calling function via new variable 
    printf("%d\n",c); 
    c = substract(11, 5); //calling function via new variable 
    printf("%d",c); 
    return 0; 
} 

salida de esto es:

Tenga en cuenta que, al igual definidor math_func se ha utilizado para declarar ambas funciones.

El mismo enfoque de typedef se puede usar para extern struct. (Usando sturuct en otro archivo.)

3

Este es el ejemplo más simple de los punteros de función y función de arrays de punteros que he escrito como un ejercicio.

typedef double (*pf)(double x); /*this defines a type pf */ 

    double f1(double x) { return(x+x);} 
    double f2(double x) { return(x*x);} 

    pf pa[] = {f1, f2}; 


    main() 
    { 
     pf p; 

     p = pa[0]; 
     printf("%f\n", p(3.0)); 
     p = pa[1]; 
     printf("%f\n", p(3.0)); 
    } 
0

Use typedefs para definir los tipos más complicados es decir, los punteros de función

voy a tomar el ejemplo de la definición de un estado de la máquina en C

typedef int (*action_handler_t)(void *ctx, void *data); 

ahora hemos definido un tipo llamado action_handler que toma dos punteros y devuelve un int

define su estado-máquina

typedef struct 
    { 
     state_t curr_state; /* Enum for the Current state */ 
     event_t event; /* Enum for the event */ 
     state_t next_state; /* Enum for the next state */ 
     action_handler_t event_handler; /* Function-pointer to the action */ 

    }state_element; 

El puntero a la acción se ve como un tipo simple y typedef sirve principalmente para este propósito.

Todos mis controladores de eventos ahora deben cumplir con el tipo definido por action_handler

int handle_event_a(void *fsm_ctx, void *in_msg); 

    int handle_event_b(void *fsm_ctx, void *in_msg); 

Referencias:

experto en programación C por Linden

Cuestiones relacionadas