I just saw a picture today and think I\'d appreciate explanations. So here is the picture:
I found this confusing and wondered if such codes are ever prac
In C, declaration mirrors usage—that’s how it’s defined in the standard. The declaration:
void (*(*f[])())()
Is an assertion that the expression (*(*f[i])())()
produces a result of type void
. Which means:
f
must be an array, since you can index it:
f[i]
The elements of f
must be pointers, since you can dereference them:
*f[i]
Those pointers must be pointers to functions taking no arguments, since you can call them:
(*f[i])()
The results of those functions must also be pointers, since you can dereference them:
*(*f[i])()
Those pointers must also be pointers to functions taking no arguments, since you can call them:
(*(*f[i])())()
Those function pointers must return void
The “spiral rule” is just a mnemonic that provides a different way of understanding the same thing.
As a random trivia factoid, you might find it amusing to know that there's an actual word in English to describe how C declarations are read: Boustrophedonically, that is, alternating right-to-left with left-to-right.
Reference: Van der Linden, 1994 - Page 76
I found method described by Bruce Eckel to be helpful and easy to follow:
Defining a function pointer
To define a pointer to a function that has no arguments and no return value, you say:
void (*funcPtr)();
When you are looking at a complex definition like this, the best way to attack it is to start in the middle and work your way out. “Starting in the middle” means starting at the variable name, which is funcPtr. “Working your way out” means looking to the right for the nearest item (nothing in this case; the right parenthesis stops you short), then looking to the left (a pointer denoted by the asterisk), then looking to the right (an empty argument list indicating a function that takes no arguments), then looking to the left (void, which indicates the function has no return value). This right-left-right motion works with most declarations.
To review, “start in the middle” (“funcPtr is a ...”), go to the right (nothing there – you're stopped by the right parenthesis), go to the left and find the ‘*’ (“... pointer to a ...”), go to the right and find the empty argument list (“... function that takes no arguments ... ”), go to the left and find the void (“funcPtr is a pointer to a function that takes no arguments and returns void”).
You may wonder why *funcPtr requires parentheses. If you didn't use them, the compiler would see:
void *funcPtr();
You would be declaring a function (that returns a void*) rather than defining a variable. You can think of the compiler as going through the same process you do when it figures out what a declaration or definition is supposed to be. It needs those parentheses to “bump up against” so it goes back to the left and finds the ‘*’, instead of continuing to the right and finding the empty argument list.
Complicated declarations & definitions
As an aside, once you figure out how the C and C++ declaration syntax works you can create much more complicated items. For instance:
//: C03:ComplicatedDefinitions.cpp /* 1. */ void * (*(*fp1)(int))[10]; /* 2. */ float (*(*fp2)(int,int,float))(int); /* 3. */ typedef double (*(*(*fp3)())[10])(); fp3 a; /* 4. */ int (*(*f4())[10])(); int main() {} ///:~
Walk through each one and use the right-left guideline to figure it out. Number 1 says “fp1 is a pointer to a function that takes an integer argument and returns a pointer to an array of 10 void pointers.”
Number 2 says “fp2 is a pointer to a function that takes three arguments (int, int, and float) and returns a pointer to a function that takes an integer argument and returns a float.”
If you are creating a lot of complicated definitions, you might want to use a typedef. Number 3 shows how a typedef saves typing the complicated description every time. It says “An fp3 is a pointer to a function that takes no arguments and returns a pointer to an array of 10 pointers to functions that take no arguments and return doubles.” Then it says “a is one of these fp3 types.” typedef is generally useful for building complicated descriptions from simple ones.
Number 4 is a function declaration instead of a variable definition. It says “f4 is a function that returns a pointer to an array of 10 pointers to functions that return integers.”
You will rarely if ever need such complicated declarations and definitions as these. However, if you go through the exercise of figuring them out you will not even be mildly disturbed with the slightly complicated ones you may encounter in real life.
Taken from: Thinking in C++ Volume 1, second edition, chapter 3, section "Function Addresses" by Bruce Eckel.
It's only a "spiral" because there happens to be, in this declaration, only one operator on each side within each level of parentheses. Claiming that you proceed "in a spiral" generally would suggest you alternate between arrays and pointers in the declaration int ***foo[][][]
when in reality all of the array levels come before any of the pointer levels.
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).
Regarding the usefulness of this, when working with shellcode you see this construct a lot:
int (*ret)() = (int(*)())code;
ret();
While not quite as syntactically complicated, this particular pattern comes up a lot.
More complete example in this SO question.
So while the usefulness to the extent in the original picture is questionable (I would suggest that any production code should be drastically simplified), there are some syntactical constructs that do come up quite a bit.