void ( *signal(int signum, void (*handler)(int)) ) (int);
Signal expects a function pointer as one parameter, what do I need to pass exactly? and also returns another function pointer, what function exactly does it return?.
Let's perhaps first make clear what we're working up to. Stealing from signal
's man page:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
Here we can more clearly see that signal is a function accepting two arguments -- an int and a function pointer -- and returning a function pointer.
The way function pointers work is pretty straightforward: You take a function pointer, make it point to a function, and then you can call the function through the pointer. For example:
#include <stdio.h>
void hello(int x) {
printf("Hello, world! x is %d.\n", x);
}
int main(void) {
void (*ptr)(int) = hello;
ptr(2); // calls hello(2)
}
The signature of signal looks scary, largely because half the rules of variable declaration syntax are something that you only need with pointers to functions and arrays, and that even then are rarely used excessively. I'll try to demystify it:
Variable declarations in C are read from the inside out and right to left. This does not make a difference when your variable is an int
or a struct foo
, but it matters for pointers, arrays and functions. The right-to-left part is fairly straightforward:
// a is an array ----+
// of pointers --+ |
// to int ----v v v
int *aa[10];
// f is a function ----+
// returning ptr -+ |
// to int -----v v v
int *foo(void);
The inside-out bit is one that is, I think, only seen with function and array pointers. This is because it is a bit mindbendy, so it makes sense to keep it out of the more common use cases. Arrays of pointers are more common than pointers to arrays, and functions returning pointers are more common than pointers to functions, so thankfully you don't need it for them. However, we need some way to declare these datatypes if we want to be able to use them.
So how does the syntax accomodate pointers to functions, and indeed combinations such as pointers to arrays of pointers?
Let's perhaps start with lesser used array pointers, because the syntax is less crowded:
int arr[10]; // is an array
int *ptrarr[10]; // is an array of pointers
int (*arrptr)[10]; // is a pointer to an array.
int *(*arrptr)[10]; // is a pointer to an array of pointers
The parentheses provide a nested structure that we read from the inside out, and right-to-left on every level:
// arrptr is a pointer -+
// to an array ---------|-------+
// of pointers -------+ | |
// to int ---------v v v v
int *(*arrptr)[10]; // is a pointer to an array.
This can be arbitrarily extended:
// foo is an array --------+
// of pointers -------+ |
// to array ----------|----|----+
// of pointers -----+ | | |
// of pointers ---+ | | | |
// to array ------|-|-|----|----|---+
// of pointers -+ | | | | | |
// to int ---v v v v v v v v
int *(* *(*foo[10])[20])[30];
Although: please don't do stuff like that.
Function pointers, then, work much the same as array pointers, except the array bounds are replaced with an argument list. We have
void f(void); // a function taking no arguments and returning nothing
void *ptrf(void) // a function taking no arguments and returning a pointer
void (*fptr)(void); // a pointer to a function taking no arguments and returning nothing
void *(*ptrfptr)(void) // a pointer to a function taking no arguments and returning a pointer
Compare this with the array pointer syntax above. This, too, can be arbitrarily extended, and function pointers can appear in argument lists (where they can be independently read). So, working up to signal
: A function
void foo(void (*fptr)(void));
is a function accepting a function pointer as an argument. Apart from the scary-looking function pointer in the argument list, this is very much a function declaration like you're used to. The scary part is a function returning a function pointer, and it looks like this:
void (*foo(void))(void);
Let's take it apart:
// foo is a function -----------------------
// returning a pointer ----------------+ |
// to a function taking no arguments --|---|-------+
// returning nothing -------------v v v v
void (*foo(void))(void);
You can see that this, too, works very much like what we've seen with array pointers. Now, we're nearly home: signal
works very much this way, only the argument lists are different. Look at it again:
// signal is a function ------------+
// returning a pointer ------+ |
// to a function taking int -|------|-------------------------------------+
// returning nothing --v v v v
void ( *signal(int signum, void (*handler)(int)) ) (int);
The argument list of signal
itself -- int signum, void (*handler)(int))
-- can be read separately, and it should hopefully hold no horrors for you anymore, now that you can see which argument list belongs to what.
But since I can't leave you without horrors: Be aware that you can combine function and array pointers. For example,
void (*foo[10])(void);
is an array of function pointers (which can, in fact, be useful), and
int (*foo(void))[10];
is a function returning a pointer to an array. This allows the construction of really horrible things like
int (*(*(*foo)[10])(void))[20];
...which is, let's see, a pointer to an array of pointers to function returning pointers to arrays. In the event that you really need a higher-order type of this sort, do use typedefs. This could be rewritten as
typedef int (*arrptr)[20];
typedef arrptr (*funcptr)(void);
funcptr (*foo)[10];
Compare that to the original declaration and tell me which makes more sense.