Como chegamos aqui
A sintaxe C para declarar pontos de função foi projetada para espelhar o uso. Considere uma declaração de função regular como esta em <math.h>
:
double round(double number);
Para ter uma variável de ponto, você pode atribuí-la ao tipo safety usando
fp = round;
você precisaria ter declarado essa fp
variável de ponto desta maneira:
double (*fp)(double number);
Então, tudo o que você precisa fazer é ver como você usaria a função e substituir o nome dessa função por uma referência de ponteiro, transformando-a round
em *fp
. No entanto, você precisa de um conjunto extra de parênteses, o que alguns diriam que o torna um pouco mais confuso.
Indiscutivelmente, isso costumava ser mais fácil no C original, que nem tinha assinatura de função, mas não vamos voltar lá, ok?
O lugar em que se torna especialmente desagradável é descobrir como declarar uma função que aceita como argumento ou retorna um ponteiro para uma função, ou ambas.
Se você tivesse uma função:
void myhandler(int signo);
você pode passá-lo para a função de sinal (3) desta maneira:
signal(SIGHUP, myhandler);
ou se você quiser manter o manipulador antigo,
old_handler = signal(SIGHUP, new_handler);
o que é bem fácil. O que é bastante fácil - nem bonito, nem fácil - é acertar as declarações.
signal(int signo, ???)
Bem, você apenas volta à sua declaração de função e troca o nome por uma referência de ponto:
signal(int sendsig, void (*hisfunc)(int gotsig));
Como você não está declarando gotsig
, talvez seja mais fácil ler se você omitir:
signal(int sendsig, void (*hisfunc)(int));
Ou talvez não. :(
Exceto que isso não é bom o suficiente, porque o sinal (3) também retorna o manipulador antigo, como em:
old_handler = signal(SIGHUP, new_handler);
Então agora você precisa descobrir como declarar tudo isso.
void (*old_handler)(int gotsig);
é suficiente para a variável que você vai atribuir. Observe que você realmente não está declarando gotsig
aqui apenas old_handler
. Então, isso é realmente o suficiente:
void (*old_handler)(int);
Isso nos leva a uma definição correta para o sinal (3):
void (*signal(int signo, void (*handler)(int)))(int);
Typedefs para o resgate
A essa altura, acho que todos concordarão que isso é uma bagunça. Às vezes, é melhor nomear suas abstrações; frequentemente, realmente. Com o direito typedef
, isso se torna muito mais fácil de entender:
typedef void (*sig_t) (int);
Agora sua própria variável manipuladora se torna
sig_t old_handler, new_handler;
e sua declaração para o sinal (3) se torna apenas
sig_t signal(int signo, sig_t handler);
que de repente é compreensível. Livrar-se do * 's também se livrar de alguns dos parênteses confuso (e eles dizem parens sempre fazer as coisas mais fáceis de entender - hah). Seu uso ainda é o mesmo:
old_handler = signal(SIGHUP, new_handler);
mas agora você tem chance de entender as declarações para old_handler
, new_handler
e até mesmo signal
quando você encontrá-los ou necessidade de escrevê-los.
Conclusão
Acontece que muito poucos programadores C são capazes de elaborar as declarações corretas para essas coisas por conta própria, sem consultar os materiais de referência.
Eu sei, porque uma vez tivemos essa mesma pergunta em nossas perguntas da entrevista para pessoas que trabalham com kernel e driver de dispositivo. :) Certamente, perdemos muitos candidatos dessa maneira quando eles caíram e queimaram no quadro branco. Mas também evitamos contratar pessoas que alegavam ter experiência anterior nessa área, mas que na verdade não podiam fazer o trabalho.
Devido a essa dificuldade generalizada, no entanto, provavelmente não é apenas sensato, mas de fato razoável, ter um caminho a percorrer todas as declarações que não exigem mais que você seja um programador de nerds triplo-alfa, sentado três sigmas acima da média, apenas para usar isso. tipo de coisa confortavelmente.
f :: (Int -> Int -> Int) -> Int -> Int