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.