Considere la signal()
función del estándar C:
extern void (*signal(int, void(*)(int)))(int);
Perfectamente oscuro y obvio: es una función que toma dos argumentos, un entero y un puntero a una función que toma un entero como argumento y no devuelve nada, y ( signal()
) devuelve un puntero a una función que toma un entero como argumento y devuelve nada.
Si tú escribes:
typedef void (*SignalHandler)(int signum);
entonces puedes declarar signal()
como:
extern SignalHandler signal(int signum, SignalHandler handler);
Esto significa lo mismo, pero generalmente se considera algo más fácil de leer. Es más claro que la función toma a int
y a SignalHandler
y devuelve a SignalHandler
.
Sin embargo, lleva un poco acostumbrarse. Sin embargo, lo único que no puede hacer es escribir una función de controlador de señal utilizando SignalHandler
typedef
en la definición de la función.
Todavía soy de la vieja escuela que prefiere invocar un puntero de función como:
(*functionpointer)(arg1, arg2, ...);
La sintaxis moderna usa solo:
functionpointer(arg1, arg2, ...);
Puedo ver por qué eso funciona: prefiero saber que necesito buscar dónde se inicializa la variable en lugar de una función llamada functionpointer
.
Sam comentó:
He visto esta explicación antes. Y luego, como es el caso ahora, creo que lo que no entendí fue la conexión entre las dos declaraciones:
extern void (*signal(int, void()(int)))(int); /*and*/
typedef void (*SignalHandler)(int signum);
extern SignalHandler signal(int signum, SignalHandler handler);
O, lo que quiero preguntar es, ¿cuál es el concepto subyacente que uno puede usar para llegar a la segunda versión que tiene? ¿Cuál es el fundamento que conecta "SignalHandler" y el primer typedef? Creo que lo que hay que explicar aquí es lo que typedef está haciendo realmente aquí.
Intentemoslo de nuevo. El primero de ellos se levanta directamente del estándar C: lo volví a escribir y verifiqué que tenía los paréntesis correctos (no hasta que lo corrija, es una galleta difícil de recordar).
En primer lugar, recuerde que typedef
introduce un alias para un tipo. Entonces, el alias es SignalHandler
, y su tipo es:
un puntero a una función que toma un entero como argumento y no devuelve nada.
La parte 'no devuelve nada' se deletrea void
; El argumento que es un entero es (confío) autoexplicativo. La siguiente notación es simplemente (o no) cómo C deletrea el puntero para funcionar tomando argumentos como se especifica y devolviendo el tipo dado:
type (*function)(argtypes);
Después de crear el tipo de controlador de señal, puedo usarlo para declarar variables, etc. Por ejemplo:
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);
}
Tenga en cuenta ¿Cómo evitar usar printf()
en un controlador de señal?
Entonces, ¿qué hemos hecho aquí, aparte de omitir 4 encabezados estándar que serían necesarios para que el código se compile limpiamente?
Las dos primeras funciones son funciones que toman un solo entero y no devuelven nada. Uno de ellos no regresa en absoluto gracias al, exit(1);
pero el otro sí regresa después de imprimir un mensaje. Tenga en cuenta que el estándar C no le permite hacer mucho dentro de un controlador de señal; POSIX es un poco más generoso en lo que está permitido, pero oficialmente no sanciona las llamadas fprintf()
. También imprimo el número de señal que se recibió. En la alarm_handler()
función, el valor siempre será, SIGALRM
ya que es la única señal para la que es un manejador, pero signal_handler()
podría obtener SIGINT
o SIGQUIT
como el número de señal porque se usa la misma función para ambos.
Luego creo una matriz de estructuras, donde cada elemento identifica un número de señal y el controlador que se instalará para esa señal. Elegí preocuparme por 3 señales; A menudo me preocupa SIGHUP
, SIGPIPE
y SIGTERM
también y si están definidos ( #ifdef
compilación condicional), pero eso complica las cosas. Probablemente también usaría POSIX en sigaction()
lugar de signal()
, pero ese es otro problema; sigamos con lo que comenzamos.
La main()
función itera sobre la lista de controladores que se instalarán. Para cada controlador, primero llama signal()
para averiguar si el proceso ignora actualmente la señal y, al hacerlo, se instala SIG_IGN
como el controlador, lo que garantiza que la señal permanezca ignorada. Si la señal no se ignoraba anteriormente, vuelve a llamar signal()
, esta vez para instalar el controlador de señal preferido. (Presumiblemente SIG_DFL
, el otro valor es el controlador de señal predeterminado para la señal.) Debido a que la primera llamada a 'signal ()' establece el controlador SIG_IGN
y signal()
devuelve el controlador de error anterior, el valor de old
después de la if
declaración debe ser SIG_IGN
, de ahí la afirmación. (Bueno, podría serSIG_ERR
si algo salió dramáticamente mal, pero luego me enteraría de eso por el disparo afirmativo).
El programa luego hace sus cosas y sale normalmente.
Tenga en cuenta que el nombre de una función puede considerarse como un puntero a una función del tipo apropiado. Cuando no aplica los paréntesis de llamadas a funciones, como en los inicializadores, por ejemplo, el nombre de la función se convierte en un puntero de función. Por eso también es razonable invocar funciones a través de la pointertofunction(arg1, arg2)
notación; cuando vea alarm_handler(1)
, puede considerar que alarm_handler
es un puntero a la función y, por alarm_handler(1)
lo tanto, es una invocación de una función a través de un puntero de función.
Entonces, hasta ahora, he demostrado que una SignalHandler
variable es relativamente sencilla de usar, siempre que tenga el tipo de valor correcto para asignarle, que es lo que proporcionan las dos funciones de controlador de señal.
Ahora volvemos a la pregunta: ¿cómo se relacionan entre sí las dos declaraciones signal()
?
Repasemos la segunda declaración:
extern SignalHandler signal(int signum, SignalHandler handler);
Si cambiamos el nombre de la función y el tipo de esta manera:
extern double function(int num1, double num2);
usted no tendría ningún problema interpretar esto como una función que toma un int
y una double
como argumentos y devuelve un double
valor (que sería tal vez mejor que no 'confesarse, si eso es problemático? -, pero tal vez debería tener cuidado de no hacer preguntas, según duro como este si es un problema).
Ahora, en lugar de ser un double
, la signal()
función toma a SignalHandler
como su segundo argumento, y devuelve uno como resultado.
La mecánica por la cual eso también se puede tratar como:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
son difíciles de explicar, así que probablemente lo arruine. Esta vez he dado los nombres de los parámetros, aunque los nombres no son críticos.
En general, en C, el mecanismo de declaración es tal que si escribe:
type var;
entonces cuando lo escribes var
representa un valor de lo dado type
. Por ejemplo:
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
En el estándar, typedef
se trata como una clase de almacenamiento en la gramática, más bien como static
y extern
son clases de almacenamiento.
typedef void (*SignalHandler)(int signum);
significa que cuando ve una variable de tipo SignalHandler
(digamos alarm_handler) invocada como:
(*alarm_handler)(-1);
el resultado tiene type void
- no hay resultado. Y (*alarm_handler)(-1);
es una invocación de alarm_handler()
con argumento -1
.
Entonces, si declaramos:
extern SignalHandler alt_signal(void);
esto significa que:
(*alt_signal)();
representa un valor nulo. Y por lo tanto:
extern void (*alt_signal(void))(int signum);
es equivalente. Ahora, signal()
es más complejo porque no solo devuelve a SignalHandler
, también acepta tanto un int como un SignalHandler
argumento:
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
Si eso todavía lo confunde, no estoy seguro de cómo ayudarlo, todavía es misterioso en algunos niveles, pero me he acostumbrado a cómo funciona y, por lo tanto, puedo decirle que si lo sigue por otros 25 años más o menos, se convertirá en una segunda naturaleza para usted (y tal vez incluso un poco más rápido si es inteligente).