D simply reads right to left:
int[]* p; // pointer to array of int
int function(char*) fp; // pointer to function with char* parameter returning an intBeing 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 in exactly the same way you would use it: 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.
It is explained in more detail at this link: https://eigenstate.org/notes/c-decl
My ideal syntax for function pointers is:
fn signal(signum: int, fp: fn(int -> void)) -> fn(int -> void)
It keeps the parameter and return types inside, makes it obvious that it's a function using the fn keyword (or func or whatever), short and readable.When you add more parameters you get `fn(int, char -> int)`. That's the only sane way to handle it and it also supports multiple return values if you want them.
Bonus: all type modifiers should be prefix like `?*MyStruct` and control flow should be postfix `task.await.match { ... }`. Every language should either have a pipe operator or let you call any function with method syntax. `x |> f |> g` is better than `g(f(x))`.
> "str is an array 10 of pointers to char"
Wow, imagine if it was possible to actually use a language like that do declare the type of the variable like that? Something like
str: array [0..9] of ^Char;
or even var str [10]*uint8
Just imagine...This comes up every so often, and while it's sort of almost true and attractive, it isn't actually correct.
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": https://cseweb.ucsd.edu/~gbournou/CSE131/rt_lt.rule.html).
I've written some C code recently, and it came to me perhaps the pointer syntax may not be ideal. I'm not sure what the ideal would be, but I think a different notation for usage and declaration could make it less confusion.
In particular I associate '*' (used as *ptr, i.e. content that ptr points to), with content, as opposed to '&' (from &var, address of var), so again '*' means content thing points to. But in declaration, when you declare 'char *ptr', which is a pointer to a char, you clearly can't read it exactly the same way ("char with content of a pointer"? More like, the content of a pointer is char). So maybe another symbol like @ (denoting "is a pointer"), or just the keyword pointer, might make things clearer, so you'd have 'char pointer ptr' (ptr is a pointer to a char, read backwards) or simply 'char @ ptr'. The shorter '@' would be justified when you have multiple pointer e.g. when working with multidimensional arrays (which are often @@@float, something like that). Just an idea that occurred me ;)
(Although I hadn't thought about pcfwik's principle that it's written as used, that makes somewhat more sense to me)*
Edit: Said otherwise, in usage syntax the convention (or at least my way of thinking) may be left-to-right, "content of" or "address of", while in declaration we read right-to-left, "is an int", or "is a pointer", and it would make sense to me that the symbol for "is a pointer" is different than the symbol for "content of"/"address of".
There is only one iteration through the spiral in any one declarator unless there are parentheses. This is because it's basically:
[pre] [pre] ... [pre] [core] [post] ... [post]
We have the core of the declarator, usually a name (or empty when omitted). On the left there is a clump of zero or more prefix declarative operators like the pointer *, and on the right postfix ones, like array and function parentheses.No matter how many there are, you only to around he spiral one time. Let's add the declaration specifiers:
[spec] ... [spec] [pre] [pre] ... [pre] [core] [post] ... [post]
------
"We declare "core" to be ..." [spec] ... [spec] [pre] [pre] ... [pre] [core] [post] ... [post]
------ ^
\_______/
"We declare "core" to be a this, that and other postfix thing ..." ________________________
/ \
[spec] ... [spec] [pre] [pre] ... [pre] [core] [post] ... [post]
------ ^
\_______/
"We declare "core"t to be a this, that and other postfix thing, of this pre, pre ... ________________________
/ \
[spec] ... [spec] [pre] [pre] ... [pre] [core] [post] ... [post]
^________/ ------ ^
\_______/
"We declare "core"t to be a this, that and other postfix thing, of this pre, pre of type/quality given by specs."For instance:
const unsigned int * * * x [][][3]
"Declare x to be a an array of arrays of arrays of 3 pointers to pointers to pointers, to const unsigned int"But parentheses override the precedence of postfix versus prefix, so that's when the path follows a spiral with multiple loops, for each nesting level:
const unsigned int *(*(*x)[][])[3]
Without parentheses, the precedence is as if implicitly there were these parentheses: const unsigned int ***(x[][][3])
i.e. postfix "binds tighter" than prefix/unary. That's the whole basis for the spiral: flipping from left to right chasing the sequences of postfix and unary operators, though all the levels of parentheses.BTW, as a matter of terminology, ISO C does not call type construction punctuators operators; only expressions have operators. In computer science terminology related to programming languages, C declarators are type constructing expressions in which the elements like [] and * are type constructing operators.
This is useful if you're far from the internet. Here's a web site that translates the gibberish to English or vice versa:
Or
apt install cdecl
on Linux.This is why I like python.
str is a duck.
The spiral rule makes no sense, I never got why it got popular.