Hacker News
Clockwise/Spiral Rule (1994)
dang
|next
[-]
C "clockwise/spiral" rule to understand declarations - https://news.ycombinator.com/item?id=42564861 - Jan 2025 (75 comments)
Clockwise/Spiral Rule - https://news.ycombinator.com/item?id=25494219 - Dec 2020 (16 comments)
The Clockwise/Spiral Rule of C declarations - https://news.ycombinator.com/item?id=12775735 - Oct 2016 (68 comments)
The “Clockwise/Spiral Rule” - https://news.ycombinator.com/item?id=8648287 - Nov 2014 (26 comments)
The Clockwise/Spiral Rule - https://news.ycombinator.com/item?id=6471202 - Sept 2013 (7 comments)
The "Clockwise/Spiral Rule" in C - https://news.ycombinator.com/item?id=5079787 - Jan 2013 (45 comments)
pcfwik
|next
|previous
[-]
It is explained in more detail at this link: https://eigenstate.org/notes/c-decl
nitrix
|root
|parent
|next
[-]
int v, *w, x[5], *y[5], (*z[5])(int, int);
Where v is an int, w is a pointer, x is an array, y is an array of pointers, z is an array of function pointers, etc.Similarly, typedef is also just a keyword in front of a regular declaration.
int foo[5];
typedef int foo[5];
int bar(void);
typedef int bar(void);
Now you can use `bar *` as a function pointer.The entire language works like this.
arjvik
|root
|parent
[-]
int v;
means that `v` is an `int`. int *w;
means that `*w` is an `int`, meaning `w` is a pointer to an `int`. int *y[5]
(note that `◌[]` has higher precedence than `*◌`, so this is `*(y[5])`) means that `*y[5]` is an `int`, so `y[5]` is a pointer to an `int`, meaning `y` is an array of `int` pointers. int (*(*kitchensink[5])(int, int))[6];
means that `(*(*kitchensink[5])(int, int))[6]` is an int, so- `*(*kitchensink[5])(int, int)` is an array of `int`.
- `(*kitchensink[5])(int, int)` is a pointer to array of `int`.
- `kitchensink[5]` is a function pointer to a function that takes `(int, int)` and returns a pointer to an array of `int`.
- `kitchensink` is an array of function pointers to functions that take `(int, int)` and return a pointer to an array of `int`.
IronFox05
|root
|parent
|next
|previous
[-]
How do you make an std::array of a given type? Wrap the existing type in an extra layer of std::array, we all know this, it makes sense, there's no reasonable alternative. How do you make a C-array of a given type? Oh boy, "prepend the array specifier before the list of existing array specifiers" (actually it's worse because you have to find the right possibly-empty array of existing array specifiers first, just because there's a list of array specifiers somewhere in the type doesn't mean it's the one you should be prepending to).
"Declaration follows use" immediately goes out the window when faced with typedeffed types being used as the base type, or (as mentioned) generics in descendant languages of C. Instead you get "declaration builds up a type by wrapping layers around a core, use breaks down a type layer by layer starting from the outside" (so, necessarily, they mirror each other). C could have worked that way, and it would have made more sense.
"Declaration follows use" is the type level equivalent of taking off your socks before taking off your shoes because that's the order in which you put them on.
clifflocked
|root
|parent
|next
[-]
There absolutely are reasonable alternate ways to represent ordered data that don't involve templates. The way that C does it makes sense in most cases, and if you are looking at something that you cannot understand, you are looking at bad code.
> "Declaration follows use" immediately goes out the window when faced with typedeffed types being used as the base type
Typedefs are an abstraction. If you create a typedef, it is usually because you only want to handle the data as a whole, passing it to helper functions that remove the typedef. Also, declaration of use does not break down with typedefs:
typedef char *(*fn)(int, char *);
fn my_fn;
char *s = (*my_fn)(0, ""); // Proper use
> "Declaration follows use" is the type level equivalent of taking off your socks before taking off your shoes because that's the order in which you put them on.Please give me an example of some C code where this is the case.
jstanley
|root
|parent
|next
|previous
[-]
std::array isn't a thing in C, so you don't.
stackghost
|root
|parent
|previous
[-]
huh? where is the extra layer?
std::array<int, 5> array_of_ints = { 1, 2, 3, 4, 5 };
>How do you make a C-array of a given type? Oh boy, "prepend the array specifier before the list of existing array specifiers"?
int c_style_array[5] = {2, 3, 5, 7, 11};
uecker
|root
|parent
|next
[-]
std::array<std::array<int, 3>, 5> array_of_ints;
int c_style_array[5][3];
But the C-style array is more readable, so I am not sure what the complaint really is about.
saghm
|root
|parent
[-]
WalterBright
|next
|previous
[-]
int[]* p; // pointer to array of int
int function(char*) fp; // pointer to function with char* parameter returning an int
tancop
|next
|previous
[-]
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))`.
bpavuk
|root
|parent
[-]
this is actually very Kotlin, in Kotlin it'd be `val lambda: (Int, Char) -> Int = { myInt, myChar -> return 69 }`
Zig has also come very close:
const Call2Op = *const fn (a: i8, b: i8) i8;
fn doOp(fnCall: Call2Op, op1: i8, op2: i8) i8 {
return fnCall(op1, op2);
}
> Every language should either have a pipe operator or let you call any function with method syntaxthere is a nicety in Zig that may help: `fn1(p1, ...)` is the same as `fn1.p1()`, so `fn2(fn1(p1))` can be unfolded into `p1.fn1().fn2()`
Joker_vD
|next
|previous
[-]
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...
stephencanon
|next
|previous
[-]
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).
jcranmer
|root
|parent
|next
[-]
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.)
mananaysiempre
|root
|parent
|previous
[-]
gnramires
|next
|previous
[-]
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".
PhilipRoman
|root
|parent
|next
|previous
[-]
jandrese
|root
|parent
|next
|previous
[-]
lelanthran
|root
|parent
[-]
That can't be the reason; IIRC Pascal has always used '^' for pointer derefencing (just like git's HEAD^^^)
dnautics
|root
|parent
|next
|previous
[-]
Joker_vD
|root
|parent
[-]
var p: ^integer, i: integer;
p := @i;
p^ := 42;
Which follows an obvious "if modifier of a base type goes to the left of the type, then the operator that uses this modifier goes to the right in the expression". Just like "array of T/[]T" translates into "arr[index]".
kazinator
|next
|previous
[-]
[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.