First, some basic rules:
T *a[N]; // a is an array of pointer to T
T (*a)[N]; // a is a pointer to an array of T
T *f(); // f is a function returning a pointer to T
T (*f)(); // f is a pointer to a function returning T
const T *p; // p is a non-const pointer to const T
T const *p; // same as above
T * const p; // p is a const pointer to non-const T
In both declarators and expressions, the []
and ()
operators have higher precedence than the *
operator, so you need to explicitly group it with the identifier when working with pointers to arrays ((*a)[N]
) and pointers to functions ((*f)()
).
When you find a hairy declaration, find the left-most identifier and work your way out, remembering the rules above, and applying them recursively to any function parameters:
signal -- signal
signal( ) -- is a function taking
signal( ) -- parameter unnamed
signal(int, ) -- of type int
signal(int, fp ) -- parameter fp
signal(int, (*fp) ) -- is a pointer
signal(int, (*fp)( )) -- to a function taking
signal(int, (*fp)( )) -- parameter unnamed
signal(int, (*fp)(int)) -- of type int
signal(int, void (*fp)(int)) -- returning void
(*signal(int, void (*fp)(int))) -- returning a pointer
(*signal(int, void (*fp)(int)))( ) -- to a function taking
(*signal(int, void (*fp)(int)))( ) -- parameter unnamed
(*signal(int, void (*fp)(int)))(int) -- of type int
void (*signal(int, void (*fp)(int)))(int); -- returning void
In English, signal
is a function that takes an integer and a pointer to a signal function as parameters and returns a pointer to a handling function.
Occasionally you don't have an identifier (as in a function prototype where only the types are specified), so you have to mentally put in a placeholder (call it λ) and apply the rules to that placeholder:
void (*signal(int λ, void (*fp)(int λ)))(int λ);