Siempre me he quedado un poco perplejo cuando leía el código de otras personas que tenía typedefs para punteros a funciones con argumentos. Recuerdo que me tomó 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 reflexiones sobre cómo escribir buenos typedefs para punteros a funciones (Do's y Do not's), en cuanto a por qué son útiles y cómo entender el trabajo de los demás? ¡Gracias!
c
function-pointers
typedef
nbro
fuente
fuente
Respuestas:
Considere la
signal()
función del estándar C:Perfectamente oscuro y obvio: es una función que toma dos argumentos, un entero y un puntero a una función que toma un entero como argumento y no devuelve nada, y (
signal()
) devuelve un puntero a una función que toma un entero como argumento y devuelve nada.Si tú escribes:
entonces puedes declarar
signal()
como:Esto significa lo mismo, pero generalmente se considera algo más fácil de leer. Es más claro que la función toma a
int
y aSignalHandler
y devuelve aSignalHandler
.Sin embargo, lleva un poco acostumbrarse. Sin embargo, lo único que no puede hacer es escribir una función de controlador de señal utilizando
SignalHandler
typedef
en la definición de la función.Todavía soy de la vieja escuela que prefiere invocar un puntero de función como:
La sintaxis moderna usa solo:
Puedo ver por qué eso funciona: prefiero saber que necesito buscar dónde se inicializa la variable en lugar de una función llamada
functionpointer
.Sam comentó:
Intentemoslo de nuevo. El primero de ellos se levanta directamente del estándar C: lo volví a escribir y verifiqué que tenía los paréntesis correctos (no hasta que lo corrija, es una galleta difícil de recordar).
En primer lugar, recuerde que
typedef
introduce un alias para un tipo. Entonces, el alias esSignalHandler
, y su tipo es:La parte 'no devuelve nada' se deletrea
void
; El argumento que es un entero es (confío) autoexplicativo. La siguiente notación es simplemente (o no) cómo C deletrea el puntero para funcionar tomando argumentos como se especifica y devolviendo el tipo dado:Después de crear el tipo de controlador de señal, puedo usarlo para declarar variables, etc. Por ejemplo:
Tenga en cuenta ¿Cómo evitar usar
printf()
en un controlador de señal?Entonces, ¿qué hemos hecho aquí, aparte de omitir 4 encabezados estándar que serían necesarios para que el código se compile limpiamente?
Las dos primeras funciones son funciones que toman un solo entero y no devuelven nada. Uno de ellos no regresa en absoluto gracias al,
exit(1);
pero el otro sí regresa después de imprimir un mensaje. Tenga en cuenta que el estándar C no le permite hacer mucho dentro de un controlador de señal; POSIX es un poco más generoso en lo que está permitido, pero oficialmente no sanciona las llamadasfprintf()
. También imprimo el número de señal que se recibió. En laalarm_handler()
función, el valor siempre será,SIGALRM
ya que es la única señal para la que es un manejador, perosignal_handler()
podría obtenerSIGINT
oSIGQUIT
como el número de señal porque se usa la misma función 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
ySIGTERM
también y si están definidos (#ifdef
compilación condicional), pero eso complica las cosas. Probablemente también usaría POSIX ensigaction()
lugar designal()
, pero ese es otro problema; sigamos con lo que comenzamos.La
main()
función itera sobre la lista de controladores que se instalarán. Para cada controlador, primero llamasignal()
para averiguar si el proceso ignora actualmente la señal y, al hacerlo, se instalaSIG_IGN
como el controlador, lo que garantiza que la señal permanezca ignorada. Si la señal no se ignoraba anteriormente, vuelve a llamarsignal()
, esta vez para instalar el controlador de señal preferido. (PresumiblementeSIG_DFL
, el otro valor es el controlador de señal predeterminado para la señal.) Debido a que la primera llamada a 'signal ()' establece el controladorSIG_IGN
ysignal()
devuelve el controlador de error anterior, el valor deold
después de laif
declaración debe serSIG_IGN
, de ahí la afirmación. (Bueno, podría serSIG_ERR
si algo salió dramáticamente mal, pero luego me enteraría de eso por el disparo afirmativo).El programa luego hace sus cosas y sale normalmente.
Tenga en cuenta que el nombre de una función puede considerarse como un puntero a una función del tipo apropiado. Cuando no aplica los paréntesis de llamadas a funciones, como en los inicializadores, por ejemplo, el nombre de la función se convierte en un puntero de función. Por eso también es razonable invocar funciones a través de la
pointertofunction(arg1, arg2)
notación; cuando veaalarm_handler(1)
, puede considerar quealarm_handler
es un puntero a la función y, poralarm_handler(1)
lo tanto, es una invocación de una función a través de un puntero de función.Entonces, hasta ahora, he demostrado que una
SignalHandler
variable es relativamente sencilla de usar, siempre que tenga el tipo de valor correcto para asignarle, que es lo que proporcionan las dos funciones de controlador de señal.Ahora volvemos a la pregunta: ¿cómo se relacionan entre sí las dos declaraciones
signal()
?Repasemos la segunda declaración:
Si cambiamos el nombre de la función y el tipo de esta manera:
usted no tendría ningún problema interpretar esto como una función que toma un
int
y unadouble
como argumentos y devuelve undouble
valor (que sería tal vez mejor que no 'confesarse, si eso es problemático? -, pero tal vez debería tener cuidado de no hacer preguntas, según duro como este si es un problema).Ahora, en lugar de ser un
double
, lasignal()
función toma aSignalHandler
como su segundo argumento, y devuelve uno como resultado.La mecánica por la cual eso también se puede tratar como:
son difíciles de explicar, así que probablemente lo arruine. 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 escribe:
entonces cuando lo escribes
var
representa un valor de lo dadotype
. Por ejemplo:En el estándar,
typedef
se trata como una clase de almacenamiento en la gramática, más bien comostatic
yextern
son clases de almacenamiento.significa que cuando ve una variable de tipo
SignalHandler
(digamos alarm_handler) invocada como:el resultado tiene
type void
- no hay resultado. Y(*alarm_handler)(-1);
es una invocación dealarm_handler()
con argumento-1
.Entonces, si declaramos:
esto significa que:
representa un valor nulo. Y por lo tanto:
es equivalente. Ahora,
signal()
es más complejo porque no solo devuelve aSignalHandler
, también acepta tanto un int como unSignalHandler
argumento:Si eso todavía lo confunde, no estoy seguro de cómo ayudarlo, todavía es misterioso en algunos niveles, pero me he acostumbrado a cómo funciona y, por lo tanto, puedo decirle que si lo sigue por otros 25 años más o menos, se convertirá en una segunda naturaleza para usted (y tal vez incluso un poco más rápido si es inteligente).
fuente
extern void (*signal(int, void(*)(int)))(int);
significa que lasignal(int, void(*)(int))
función devolverá un puntero de función avoid f(int)
. Cuando desee especificar un puntero de función como valor de retorno , la sintaxis se complica. Debe colocar el tipo de valor de retorno a la izquierda y la lista de argumentos a la derecha , mientras que es el medio que está definiendo. Y en este caso, lasignal()
función en sí misma toma un puntero de función como parámetro, lo que complica aún más las cosas. La buena noticia es que si puedes leer esta, la Fuerza ya está contigo. :).&
frente al nombre de una función? Es totalmente innecesario; sin sentido, incluso. Y definitivamente no es "vieja escuela". La vieja escuela usa un nombre de función simple y llanamente.Un puntero de función es como cualquier otro puntero, pero apunta a la dirección de una función en lugar de la dirección de datos (en el montón o pila). Como 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 se acepta el tipo de cada parámetro. Cuando escribe def una definición, le da un 'nombre descriptivo' que facilita la creación y referencia de punteros utilizando esa definición.
Entonces, por ejemplo, suponga que tiene una función:
entonces el siguiente typedef:
se puede usar para señalar esta
doMulitplication
función. Simplemente está definiendo un puntero a una función que devuelve un flotante y toma dos parámetros, cada uno de tipo flotante. Esta definición tiene el nombre descriptivopt2Func
. Tenga en cuenta quept2Func
puede apuntar a CUALQUIER función que devuelva un flotador y tome 2 flotadores.Por lo tanto, puede crear un puntero que apunte a la función doMultiplication de la siguiente manera:
y puede invocar la función usando este puntero de la siguiente manera:
Esto es una buena lectura: http://www.newty.de/fpt/index.html
fuente
pt2Func myFnPtr = &doMultiplication;
lugar dept2Func *myFnPtr = &doMultiplication;
comomyFnPtr
ya es un puntero.myFunPtr
ya es un puntero de función así que el usopt2Func myFnPtr = &doMultiplication;
Una manera muy fácil de entender typedef del puntero de función:
fuente
cdecl
es una gran herramienta para descifrar sintaxis extraña como declaraciones de puntero de función. Puedes usarlo para generarlos también.En cuanto a los consejos para hacer que las declaraciones complicadas sean más fáciles de analizar para el mantenimiento futuro (usted mismo u otros), recomiendo hacer
typedef
pequeños fragmentos y usar esas piezas pequeñas como bloques de construcción para expresiones más grandes y más complicadas. Por ejemplo:más bien que:
cdecl
puede ayudarte con estas cosas:Y es (de hecho) exactamente cómo generé ese loco desastre anterior.
fuente
La salida de esto es:
22
6 6
Tenga en cuenta que se ha utilizado el mismo definidor math_func para declarar tanto la función.
Se puede usar el mismo enfoque de typedef para la estructura externa (usando sturuct en otro archivo).
fuente
Utilice typedefs para definir tipos más complicados, es decir, punteros de función
Tomaré el ejemplo de definir una máquina de estado en C
ahora hemos definido un tipo llamado action_handler que toma dos punteros y devuelve un int
define tu máquina de estado
El puntero de función a la acción parece 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
Referencias
Programación experta en C por Linden
fuente
Este es el ejemplo más simple de punteros de función y matrices de puntero de función que escribí como ejercicio.
fuente