Cómo llegamos aquí
La sintaxis de C para declarar puntos de función tenía la intención de reflejar el uso. Considere una declaración de función regular como esta de <math.h>
:
double round(double number);
Para tener una variable de punto, puede asignarla con seguridad de tipo usando
fp = round;
deberías haber declarado esa fp
variable de punto de esta manera:
double (*fp)(double number);
Entonces, todo lo que tiene que hacer es ver cómo usaría la función y reemplazar el nombre de esa función con una referencia de puntero, convirtiéndola round
en *fp
. Sin embargo, necesita un conjunto adicional de parens, lo que algunos dirían que lo hace un poco más desordenado.
Podría decirse que esto solía ser más fácil en el C original, que ni siquiera tenía la firma de la función, pero no volvamos allí, ¿de acuerdo?
El lugar en el que se vuelve especialmente desagradable es descubrir cómo declarar una función que toma como argumento o devuelve un puntero a una función, o ambas cosas.
Si tuvieras una función:
void myhandler(int signo);
podría pasarlo a la función de señal (3) de esta manera:
signal(SIGHUP, myhandler);
o si quieres conservar el antiguo controlador, entonces
old_handler = signal(SIGHUP, new_handler);
Lo cual es bastante fácil. Lo que es bastante fácil, ni bonito ni fácil, es hacer las declaraciones correctas.
signal(int signo, ???)
Bueno, simplemente regrese a su declaración de función e intercambie el nombre por una referencia de punto:
signal(int sendsig, void (*hisfunc)(int gotsig));
Debido a que no está declarando gotsig
, puede que le resulte más fácil de leer si omite:
signal(int sendsig, void (*hisfunc)(int));
O tal vez no. :(
Excepto que eso no es lo suficientemente bueno, porque la señal (3) también devuelve el controlador antiguo, como en:
old_handler = signal(SIGHUP, new_handler);
Así que ahora tienes que descubrir cómo declarar todo eso.
void (*old_handler)(int gotsig);
es suficiente para la variable a la que va a asignar. Tenga en cuenta que no está declarando realmente gotsig
aquí, solo old_handler
. Entonces esto es realmente suficiente:
void (*old_handler)(int);
Eso nos lleva a una definición correcta de señal (3):
void (*signal(int signo, void (*handler)(int)))(int);
Typedefs al rescate
En este momento, creo que todos estarán de acuerdo en que eso es un desastre. A veces es mejor nombrar tus abstracciones; a menudo, de verdad. Con la derecha typedef
, esto se vuelve mucho más fácil de entender:
typedef void (*sig_t) (int);
Ahora su propia variable de controlador se convierte en
sig_t old_handler, new_handler;
y su declaración para la señal (3) se vuelve justa
sig_t signal(int signo, sig_t handler);
que de repente es comprensible Deshacerse de los * también elimina algunos de los paréntesis confusos (y dicen que los padres siempre hacen las cosas más fáciles de entender, ¡ja!). Su uso sigue siendo el mismo:
old_handler = signal(SIGHUP, new_handler);
pero ahora tiene la oportunidad de comprender las declaraciones de old_handler
, new_handler
e incluso signal
cuando las encuentra por primera vez o necesita escribirlas.
Conclusión
Resulta que muy pocos programadores de C son capaces de idear las declaraciones correctas para estas cosas por sí mismos sin consultar los materiales de referencia.
Lo sé, porque una vez tuvimos esta misma pregunta en nuestras preguntas de entrevista para personas que trabajan en el núcleo y el controlador del dispositivo. :) Claro, perdimos muchos candidatos de esa manera cuando se estrellaron y quemaron en la pizarra. Pero también evitamos contratar personas que afirmaran que tenían experiencia previa en esta área pero que en realidad no podían hacer el trabajo.
Sin embargo, debido a esta dificultad generalizada, probablemente no solo sea sensato, sino que sea razonable tener una forma de hacer todas esas declaraciones que ya no requieren que seas un programador geek de triple alfa sentado tres sigmas por encima de la media solo para usar esto tipo de cosas cómodamente.
f :: (Int -> Int -> Int) -> Int -> Int