Comment nous sommes arrivés ici
La syntaxe C pour déclarer des points de fonction était destinée à refléter l'utilisation. Considérez une déclaration de fonction régulière comme ceci à partir de <math.h>
:
double round(double number);
Pour avoir une variable ponctuelle, vous pouvez l'affecter à la sécurité de type en utilisant
fp = round;
vous devez avoir déclaré cette fp
variable de point de cette façon:
double (*fp)(double number);
Donc , tout ce que vous devez faire est de regarder la façon dont vous utilisez la fonction, et remplacer le nom de cette fonction avec une référence de pointeur, ce qui round
en *fp
. Cependant, vous avez besoin d'un ensemble supplémentaire de parens, ce qui, selon certains, le rend un peu plus compliqué.
On peut dire que cela était plus facile dans le C d'origine, qui n'avait même pas de signature de fonction, mais n'y retournons pas, d'accord?
L'endroit où cela devient particulièrement désagréable est de savoir comment déclarer une fonction qui prend comme argument ou renvoie un pointeur sur une fonction, ou les deux.
Si vous aviez une fonction:
void myhandler(int signo);
vous pouvez le passer à la fonction signal (3) de cette façon:
signal(SIGHUP, myhandler);
ou si vous souhaitez conserver l'ancien gestionnaire,
old_handler = signal(SIGHUP, new_handler);
ce qui est assez facile. Ce qui est assez facile - ni joli, ni facile - c'est de bien faire les déclarations.
signal(int signo, ???)
Eh bien, revenez simplement à votre déclaration de fonction et échangez le nom pour une référence de point:
signal(int sendsig, void (*hisfunc)(int gotsig));
Parce que vous ne déclarez pas gotsig
, vous pourriez trouver plus facile à lire si vous omettez:
signal(int sendsig, void (*hisfunc)(int));
Ou peut être pas. :(
Sauf que ce n'est pas suffisant, car signal (3) renvoie également l'ancien gestionnaire, comme dans:
old_handler = signal(SIGHUP, new_handler);
Alors maintenant, vous devez comprendre comment déclarer tout cela.
void (*old_handler)(int gotsig);
est suffisant pour la variable à laquelle vous allez attribuer. Notez que vous ne déclarez pas vraiment gotsig
ici, seulement old_handler
. C'est vraiment suffisant:
void (*old_handler)(int);
Cela nous amène à une définition correcte du signal (3):
void (*signal(int signo, void (*handler)(int)))(int);
Typedefs à la rescousse
À ce moment-là, je pense que tout le monde conviendra que c'est un gâchis. Parfois, il vaut mieux nommer vos abstractions; souvent, vraiment. Avec le droit typedef
, cela devient beaucoup plus facile à comprendre:
typedef void (*sig_t) (int);
Maintenant, votre propre variable de gestionnaire devient
sig_t old_handler, new_handler;
et votre déclaration pour le signal (3) devient juste
sig_t signal(int signo, sig_t handler);
ce qui est soudainement compréhensible. Se débarrasser des * supprime également certaines parenthèses déroutantes (et ils disent que les parens rendent toujours les choses plus faciles à comprendre - hah!). Votre utilisation est toujours la même:
old_handler = signal(SIGHUP, new_handler);
mais maintenant vous avez la possibilité de comprendre les déclarations pour old_handler
, new_handler
et même signal
lorsque vous les rencontrez pour la première fois ou devez les écrire.
Conclusion
Il se trouve que très peu de programmeurs C sont capables de concevoir par eux-mêmes les déclarations correctes pour ces choses sans consulter les documents de référence.
Je sais, parce que nous avions une fois cette question sur nos questions d'entrevue pour les personnes qui travaillent sur le noyau et les pilotes de périphérique. :) Bien sûr, nous avons perdu beaucoup de candidats de cette façon car ils se sont écrasés et ont brûlé sur le tableau blanc. Mais nous avons également évité d'embaucher des personnes qui prétendaient avoir une expérience antérieure dans ce domaine mais qui ne pouvaient pas réellement faire le travail.
En raison de cette difficulté généralisée, cependant, il est probablement non seulement raisonnable mais aussi raisonnable d'avoir un moyen de traiter toutes ces déclarations qui ne nécessitent plus que vous soyez un programmeur geek triple-alpha assis trois sigmas au-dessus de la moyenne juste pour utiliser cela. sorte de chose confortablement.
f :: (Int -> Int -> Int) -> Int -> Int