Considere a signal()
função do padrão C:
extern void (*signal(int, void(*)(int)))(int);
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:
typedef void (*SignalHandler)(int signum);
então você pode declarar signal()
como:
extern SignalHandler signal(int signum, SignalHandler handler);
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 a SignalHandler
e retorna a SignalHandler
.
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:
(*functionpointer)(arg1, arg2, ...);
A sintaxe moderna usa apenas:
functionpointer(arg1, arg2, ...);
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:
Eu já vi essa explicação antes. E então, como é o caso agora, acho que o que não recebi foi a conexão entre as duas declarações:
extern void (*signal(int, void()(int)))(int); /*and*/
typedef void (*SignalHandler)(int signum);
extern SignalHandler signal(int signum, SignalHandler handler);
Ou, o que quero perguntar é: qual é o conceito subjacente que se pode usar para criar a segunda versão que você tem? Qual é o fundamental que conecta "SignalHandler" e o primeiro typedef? Eu acho que o que precisa ser explicado aqui é o que o typedef está realmente fazendo aqui.
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 é:
um ponteiro para uma função que usa um número inteiro como argumento e não retorna nada.
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:
type (*function)(argtypes);
Depois de criar o tipo de manipulador de sinal, posso usá-lo para declarar variáveis e assim por diante. Por exemplo:
static void alarm_catcher(int signum)
{
fprintf(stderr, "%s() called (%d)\n", __func__, signum);
}
static void signal_catcher(int signum)
{
fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum);
exit(1);
}
static struct Handlers
{
int signum;
SignalHandler handler;
} handler[] =
{
{ SIGALRM, alarm_catcher },
{ SIGINT, signal_catcher },
{ SIGQUIT, signal_catcher },
};
int main(void)
{
size_t num_handlers = sizeof(handler) / sizeof(handler[0]);
size_t i;
for (i = 0; i < num_handlers; i++)
{
SignalHandler old_handler = signal(handler[i].signum, SIG_IGN);
if (old_handler != SIG_IGN)
old_handler = signal(handler[i].signum, handler[i].handler);
assert(old_handler == SIG_IGN);
}
...continue with ordinary processing...
return(EXIT_SUCCESS);
}
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 chamadas fprintf()
. Também imprimo o número do sinal recebido. Na alarm_handler()
função, o valor sempre será SIGALRM
como esse é o único sinal para o qual ele é manipulador, mas signal_handler()
pode obter SIGINT
ou SIGQUIT
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
e SIGTERM
também e se eles são definidos ( #ifdef
compilação condicional), mas isso apenas complica as coisas. Eu provavelmente também usaria POSIX em sigaction()
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 chama signal()
para descobrir se o processo está atualmente ignorando o sinal e, enquanto isso, é instalado SIG_IGN
como manipulador, o que garante que o sinal permaneça ignorado. Se o sinal não estava sendo ignorado anteriormente, ele chama signal()
novamente, desta vez para instalar o manipulador de sinal preferido. (O outro valor é presumivelmente SIG_DFL
, o manipulador de sinal padrão para o sinal.) Como a primeira chamada para 'signal ()' define o manipulador SIG_IGN
e signal()
retorna o manipulador de erro anterior, o valor de old
após a if
instrução deve ser SIG_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 que alarm_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:
extern SignalHandler signal(int signum, SignalHandler handler);
Se mudarmos o nome da função e o tipo como este:
extern double function(int num1, double num2);
você não teria problemas em interpretar isso como uma função que recebe um int
e um double
como argumentos e retorna um double
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 a SignalHandler
como segundo argumento e retorna um como resultado.
A mecânica pela qual isso também pode ser tratado como:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
é 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:
type var;
então, quando você escreve var
, representa um valor do dado type
. Por exemplo:
int i; // i is an int
int *ip; // *ip is an int, so ip is a pointer to an integer
int abs(int val); // abs(-1) is an int, so abs is a (pointer to a)
// function returning an int and taking an int argument
No padrão, typedef
é tratada como uma classe de armazenamento na gramática, um pouco como static
e extern
são classes de armazenamento.
typedef void (*SignalHandler)(int signum);
significa que quando você vê uma variável do tipo SignalHandler
(digamos alarm_handler) chamada como:
(*alarm_handler)(-1);
o resultado tem type void
- não há resultado. E (*alarm_handler)(-1);
é uma invocação de alarm_handler()
com argumento -1
.
Então, se declaramos:
extern SignalHandler alt_signal(void);
significa que:
(*alt_signal)();
representa um valor nulo. E portanto:
extern void (*alt_signal(void))(int signum);
é equivalente. Agora, signal()
é mais complexo porque não apenas retorna a SignalHandler
, mas também aceita os SignalHandler
argumentos int e a as:
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
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).