Being taught this rule in undergrad really hampered my appreciation of C. As I've said in a previous comment, the real key that unlocked understanding C declarations for me is the mantra "declaration follows use." You declare a variable in C <i>in exactly the same way you would use it:</i> if you know how to use a variable, then you know how to read and write a declaration for it. Once I understood this elegant idea, it became hard to enjoy using statically typed languages that eschew it.<p>It is explained in more detail at this link:
<a href="https://eigenstate.org/notes/c-decl" rel="nofollow">https://eigenstate.org/notes/c-decl</a>
> "str is an array 10 of pointers to char"<p>Wow, imagine if it was possible to actually use a language like that do declare the type of the variable like that? Something like<p><pre><code> str: array [0..9] of ^Char;
</code></pre>
or even<p><pre><code> var str [10]*uint8
</code></pre>
Just imagine...
This comes up every so often, and while it's sort of almost true and attractive, it isn't actually correct.<p>The correct rule is "follow the C grammar". An easier to remember and also correct rule is "start at the identifier being declared; work outwards from that point, reading right until you hit a closing parenthesis, then left until you hit the corresponding open parenthesis, then resume reading right..." (this is sometimes called the "right-left rule": <a href="https://cseweb.ucsd.edu/~gbournou/CSE131/rt_lt.rule.html" rel="nofollow">https://cseweb.ucsd.edu/~gbournou/CSE131/rt_lt.rule.html</a>).
The best summary--and easiest to remember, IMHO--is that variables are declared as they are used.<p>Want to write an array of function pointers that return a pointer to an array of pointers to int? Well, that's:<p><pre><code> 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]
</code></pre>
It doesn't make it all that easy to read, but you can at least write the complex types pretty reliably.<p>(The real answer is of course to just typedef every function pointer type or pointer-to-array and not worry about it anymore.)
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.)
This is useful if you're far from the internet. Here's a web site that translates the gibberish to English or vice versa:<p><a href="https://cdecl.org/" rel="nofollow">https://cdecl.org/</a><p>Or<p><pre><code> apt install cdecl
</code></pre>
on Linux.
This is why I like python.<p>str is a duck.