upvote
The best summary--and easiest to remember, IMHO--is that variables are declared as they are used.

Want to write an array of function pointers that return a pointer to an array of pointers to int? Well, that's:

  array ... -> x[N]
  ... of function pointers ... -> T (*x[N])()
  ... that return a pointer ... -> T *(*x[N])()
  ... to an array ... -> T (*(*x[N])())[M]
  ... of pointers ... -> T *(*(*x[N])())[M]
  ... to int ... -> int *(*(*x[N])())[M]
It doesn't make it all that easy to read, but you can at least write the complex types pretty reliably.

(The real answer is of course to just typedef every function pointer type or pointer-to-array and not worry about it anymore.)

reply
Why would you do that? Ignoring for the moment arguments of prototypes and sizes of arrays, read C declarations the way they were designed: “char *a[<whatever>]” means that the expression *a[<whatever>] has type char; “char (*a(<whatever>))[<whatever>]” means that the expression (*a(<whatever>))[<whatever>] has type char; and so on. Then you apply the normal precedence rules for expressions, and in this case only knowing them for the prefix and postfix operators is sufficient. (Hint: all prefix operators have one precedence and all postfix ones another, and you know which one binds tighter if you know what *i++ means.)
reply