The "spiral" rule kind of falls out of the following precedence rules:
T *a[] -- a is an array of pointer to T
T (*a)[] -- 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
The subscript []
and function call ()
operators have higher precedence than unary *
, so *f()
is parsed as *(f())
and *a[]
is parsed as *(a[])
.
So if you want a pointer to an array or a pointer to a function, then you need to explicitly group the *
with the identifier, as in (*a)[]
or (*f)()
.
Then you realize that a
and f
can be more complicated expressions than just identifiers; in T (*a)[N]
, a
could be a simple identifier, or it could be a function call like (*f())[N]
(a
-> f()
), or it could be an array like (*p[M])[N]
, (a
-> p[M]
), or it could be an array of pointers to functions like (*(*p[M])())[N]
(a
-> (*p[M])()
), etc.
It would be nice if the indirection operator *
was postfix instead of unary, which would make declarations somewhat easier to read from left to right (void f[]*()*();
definitely flows better than void (*(*f[])())()
), but it's not.
When you come across a hairy declaration like that, start by finding the leftmost identifier and apply the precedence rules above, recursively applying them to any function parameters:
f -- f
f[] -- is an array
*f[] -- of pointers ([] has higher precedence than *)
(*f[])() -- to functions
*(*f[])() -- returning pointers
(*(*f[])())() -- to functions
void (*(*f[])())(); -- returning void
The signal
function in the standard library is probably the type specimen for this kind of insanity:
signal -- signal
signal( ) -- is a function with parameters
signal( sig, ) -- sig
signal(int sig, ) -- which is an int and
signal(int sig, func ) -- func
signal(int sig, *func ) -- which is a pointer
signal(int sig, (*func)(int)) -- to a function taking an int
signal(int sig, void (*func)(int)) -- returning void
*signal(int sig, void (*func)(int)) -- returning a pointer
(*signal(int sig, void (*func)(int)))(int) -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int); -- and returning void
At this point most people say "use typedefs", which is certainly an option:
typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);
innerfunc *f[N];
But...
How would you use f
in an expression? You know it's an array of pointers, but how do you use it to execute the correct function? You have to go over the typedefs and puzzle out the correct syntax. By contrast, the "naked" version is pretty eyestabby, but it tells you exactly how to use f
in an expression (namely, (*(*f[i])())();
, assuming neither function takes arguments).