The rule is correct. However, one should be very careful in applying it.
I suggest to apply it in a more formal way for C99+ declarations.
The most important thing here is to recognize the following recursive structure of all declarations (const
, volatile
, static
, extern
, inline
, struct
, union
, typedef
are removed from the picture for simplicity but can be added back easily):
base-type [derived-part1: *'s] [object] [derived-part2: []'s or ()]
Yep, that's it, four parts.
where
base-type is one of the following (I'm using a bit compressed notation):
void
[signed/unsigned] char
[signed/unsigned] short [int]
signed/unsigned [int]
[signed/unsigned] long [long] [int]
float
[long] double
etc
object is
an identifier
OR
([derived-part1: *'s] [object] [derived-part2: []'s or ()])
* is *, denotes a reference/pointer and can be repeated
[] in derived-part2 denotes bracketed array dimensions and can be repeated
() in derived-part2 denotes parenthesized function parameters delimited with ,'s
[] elsewhere denotes an optional part
() elsewhere denotes parentheses
Once you've got all 4 parts parsed,
[object
] is [derived-part2
(containing/returning)] [derived-part2
(pointer to)] base-type
1.
If there's recursion, you find your object
(if there's any) at the bottom of the recursion stack, it'll be the inner-most one and you'll get the full declaration by going back up and collecting and combining derived parts at each level of recursion.
While parsing you may move [object]
to after [derived-part2]
(if any). This will give you a linearized, easy to understand, declaration (see 1 above).
Thus, in
char* (**(*foo[3][5])(void))[7][9];
you get:
base-type
= char
- level 1:
derived-part1
= *
, object
= (**(*foo[3][5])(void))
, derived-part2
= [7][9]
- level 2:
derived-part1
= **
, object
= (*foo[3][5])
, derived-part2
= (void)
- level 3:
derived-part1
= *
, object
= foo
, derived-part2
= [3][5]
From there:
- level 3:
*
[3][5]
foo
- level 2:
**
(void)
*
[3][5]
foo
- level 1:
*
[7][9]
**
(void)
*
[3][5]
foo
- finally,
char
*
[7][9]
**
(void)
*
[3][5]
foo
Now, reading right to left:
foo
is an array of 3 arrays of 5 pointers to a function (taking no params) returning a pointer to a pointer to an array of 7 arrays of 9 pointers to a char.
You could reverse the array dimensions in every derived-part2
in the process as well.
That's your spiral rule.
And it's easy to see the spiral. You dive into the ever more deeply nested [object]
from the left and then resurface on the right only to note that on the upper level there's another pair of left and right and so on.