Sempre fiquei um pouco perplexo quando leio o código de outras pessoas que tinha typedefs para ponteiros para funções com argumentos. Lembro-me de que demorei um pouco para chegar a essa definição enquanto tentava entender um algoritmo numérico escrito em C há um tempo atrás. Então, você poderia compartilhar suas dicas e pensamentos sobre como escrever bons typedefs para ponteiros para funções (faça e não faça), por que eles são úteis e como entender o trabalho de outras pessoas? Obrigado!
c
function-pointers
typedef
nbro
fonte
fonte
Respostas:
Considere a
signal()
função do padrão C:Perfeitamente obscuramente óbvio - é uma função que leva dois argumentos, um número inteiro e um ponteiro para uma função que leva um número inteiro como argumento e não retorna nada, e ele (
signal()
) retorna um ponteiro para uma função que leva um número inteiro como argumento e retorna nada.Se você escrever:
então você pode declarar
signal()
como:Isso significa a mesma coisa, mas geralmente é considerado um pouco mais fácil de ler. É mais claro que a função pega um
int
e aSignalHandler
e retorna aSignalHandler
.Demora um pouco para se acostumar, no entanto. A única coisa que você não pode fazer é escrever uma função de manipulador de sinal usando o
SignalHandler
typedef
na definição de função.Eu ainda sou da velha escola que prefere chamar um ponteiro de função como:
A sintaxe moderna usa apenas:
Eu posso ver por que isso funciona - eu apenas prefiro saber que eu preciso procurar onde a variável é inicializada e não para uma função chamada
functionpointer
.Sam comentou:
Vamos tentar de novo. O primeiro deles é retirado diretamente do padrão C - redigitei-o e verifiquei se os parênteses estavam corretos (até corrigi-lo - é um biscoito difícil de lembrar).
Antes de tudo, lembre-se de que
typedef
introduz um alias para um tipo. Portanto, o alias éSignalHandler
e seu tipo é:A parte "não retorna nada" está escrita
void
; o argumento que é um número inteiro é (eu confio) auto-explicativo. A seguinte notação é simplesmente (ou não) como C soletra o ponteiro para funcionar, recebendo argumentos conforme especificado e retornando o tipo especificado:Depois de criar o tipo de manipulador de sinal, posso usá-lo para declarar variáveis e assim por diante. Por exemplo:
Observe como evitar o uso
printf()
em um manipulador de sinais?Então, o que fizemos aqui - além de omitir 4 cabeçalhos padrão que seriam necessários para tornar o código compilado corretamente?
As duas primeiras funções são funções que usam um único número inteiro e não retornam nada. Um deles, na verdade, não retorna, graças ao
exit(1);
mas o outro retorna após a impressão de uma mensagem. Esteja ciente de que o padrão C não permite que você faça muito dentro de um manipulador de sinal; O POSIX é um pouco mais generoso no que é permitido, mas oficialmente não sanciona as chamadasfprintf()
. Também imprimo o número do sinal recebido. Naalarm_handler()
função, o valor sempre seráSIGALRM
como esse é o único sinal para o qual ele é manipulador, massignal_handler()
pode obterSIGINT
ouSIGQUIT
como o número do sinal, porque a mesma função é usada para ambos.Em seguida, crio uma matriz de estruturas, em que cada elemento identifica um número de sinal e o manipulador a ser instalado para esse sinal. Eu escolhi me preocupar com três sinais; Eu costumava me preocupar
SIGHUP
,SIGPIPE
eSIGTERM
também e se eles são definidos (#ifdef
compilação condicional), mas isso apenas complica as coisas. Eu provavelmente também usaria POSIX emsigaction()
vez designal()
, mas isso é outra questão; vamos ficar com o que começamos.A
main()
função itera sobre a lista de manipuladores a serem instalados. Para cada manipulador, ele primeiro chamasignal()
para descobrir se o processo está atualmente ignorando o sinal e, enquanto isso, é instaladoSIG_IGN
como manipulador, o que garante que o sinal permaneça ignorado. Se o sinal não estava sendo ignorado anteriormente, ele chamasignal()
novamente, desta vez para instalar o manipulador de sinal preferido. (O outro valor é presumivelmenteSIG_DFL
, o manipulador de sinal padrão para o sinal.) Como a primeira chamada para 'signal ()' define o manipuladorSIG_IGN
esignal()
retorna o manipulador de erro anterior, o valor deold
após aif
instrução deve serSIG_IGN
- daí a afirmação. (Bem, poderia serSIG_ERR
se algo desse dramaticamente errado - mas eu aprenderia sobre isso com a afirmação.)O programa então faz suas coisas e sai normalmente.
Observe que o nome de uma função pode ser considerado como um ponteiro para uma função do tipo apropriado. Quando você não aplica os parênteses da chamada de função - como nos inicializadores, por exemplo - o nome da função se torna um ponteiro de função. É também por isso que é razoável invocar funções por meio da
pointertofunction(arg1, arg2)
notação; quando você vêalarm_handler(1)
, pode considerar quealarm_handler
é um ponteiro para a função e, portanto,alarm_handler(1)
é uma invocação de uma função por meio de um ponteiro de função.Então, até agora, mostrei que uma
SignalHandler
variável é relativamente simples de usar, desde que você tenha o tipo certo de valor para atribuir a ela - que é o que as duas funções do manipulador de sinal fornecem.Agora voltamos à pergunta - como as duas declarações se
signal()
relacionam.Vamos revisar a segunda declaração:
Se mudarmos o nome da função e o tipo como este:
você não teria problemas em interpretar isso como uma função que recebe um
int
e umdouble
como argumentos e retorna umdouble
valor (você faria? talvez seja melhor você não confessar se isso for problemático - mas talvez você deva ser cauteloso ao fazer perguntas tão difíceis como este se for um problema).Agora, em vez de ser um
double
, osignal()
função assume aSignalHandler
como segundo argumento e retorna um como resultado.A mecânica pela qual isso também pode ser tratado como:
é complicado de explicar - então provavelmente vou estragar tudo. Dessa vez, dei os nomes dos parâmetros - embora os nomes não sejam críticos.
Em geral, em C, o mecanismo de declaração é tal que se você escrever:
então, quando você escreve
var
, representa um valor do dadotype
. Por exemplo:No padrão,
typedef
é tratada como uma classe de armazenamento na gramática, um pouco comostatic
eextern
são classes de armazenamento.significa que quando você vê uma variável do tipo
SignalHandler
(digamos alarm_handler) chamada como:o resultado tem
type void
- não há resultado. E(*alarm_handler)(-1);
é uma invocação dealarm_handler()
com argumento-1
.Então, se declaramos:
significa que:
representa um valor nulo. E portanto:
é equivalente. Agora,
signal()
é mais complexo porque não apenas retorna aSignalHandler
, mas também aceita osSignalHandler
argumentos int e a as:Se isso ainda o confunde, não tenho certeza de como ajudar - ele ainda é misterioso em alguns níveis, mas me acostumei a como funciona e, portanto, posso dizer que, se você continuar por mais 25 anos ou assim, se tornará uma segunda natureza para você (e talvez até um pouco mais rápido se você for esperto).
fonte
extern void (*signal(int, void(*)(int)))(int);
meios para os quais asignal(int, void(*)(int))
função retornará um ponteiro de funçãovoid f(int)
. Quando você deseja especificar um ponteiro de função como o valor de retorno , a sintaxe fica complicada. Você deve colocar o tipo de valor de retorno à esquerda e a lista de argumentos à direita , enquanto este é o meio que você está definindo. E, nesse caso, asignal()
própria função usa um ponteiro de função como parâmetro, o que complica ainda mais as coisas. A boa notícia é que, se você pode ler este, a Força já está com você. :).&
na frente de um nome de função? É totalmente desnecessário; inútil, até. E definitivamente não é "velha escola". Old school usa um nome de função simples e simples.Um ponteiro de função é como qualquer outro ponteiro, mas aponta para o endereço de uma função em vez do endereço de dados (na pilha ou pilha). Como qualquer ponteiro, ele precisa ser digitado corretamente. As funções são definidas pelo valor de retorno e pelos tipos de parâmetros que eles aceitam. Portanto, para descrever completamente uma função, você deve incluir seu valor de retorno e o tipo de cada parâmetro é aceito. Quando você digita essa definição, atribui-lhe um 'nome amigável', o que facilita a criação e referência de ponteiros usando essa definição.
Por exemplo, suponha que você tenha uma função:
então o seguinte typedef:
pode ser usado para apontar para esta
doMulitplication
função. É simplesmente definir um ponteiro para uma função que retorna um float e aceita dois parâmetros, cada um do tipo float. Esta definição tem o nome amigávelpt2Func
. Observe quept2Func
pode apontar para QUALQUER função que retorne um float e receba 2 floats.Portanto, você pode criar um ponteiro que aponte para a função doMultiplication da seguinte maneira:
e você pode chamar a função usando este ponteiro da seguinte maneira:
Isso faz uma boa leitura: http://www.newty.de/fpt/index.html
fonte
pt2Func myFnPtr = &doMultiplication;
vez dept2Func *myFnPtr = &doMultiplication;
comomyFnPtr
já é um ponteiro.myFunPtr
já é um ponteiro de função de modo utilizaçãopt2Func myFnPtr = &doMultiplication;
Uma maneira muito fácil de entender o typedef do ponteiro de função:
fonte
cdecl
é uma ótima ferramenta para decifrar sintaxe estranha como declarações de ponteiros de função. Você pode usá-lo para gerá-los também.Quanto a dicas para facilitar a análise de declarações complicadas para manutenção futura (por você ou por outras pessoas), recomendo criar
typedef
pequenos pedaços e usar esses pequenos pedaços como blocos de construção para expressões maiores e mais complicadas. Por exemplo:ao invés de:
cdecl
pode ajudá-lo com essas coisas:E é (de fato) exatamente como eu gere essa bagunça louca acima.
fonte
Saída disso é:
22
6
Observe que o mesmo definidor math_func foi usado para declarar a função.
A mesma abordagem do typedef pode ser usada para estrutura externa. (Usando sturuct em outro arquivo.)
fonte
Use typedefs para definir tipos mais complicados, como ponteiros de função
Vou dar o exemplo de definir uma máquina de estado em C
agora definimos um tipo chamado action_handler que pega dois ponteiros e retorna um int
defina sua máquina de estado
O ponteiro de função para a ação parece um tipo simples e o typedef serve principalmente para esse propósito.
Todos os meus manipuladores de eventos agora devem aderir ao tipo definido por action_handler
Referências:
Programação C especializada por Linden
fonte
Este é o exemplo mais simples de ponteiros de função e matrizes de ponteiros de função que escrevi como exercício.
fonte