5 ms·
Clockwise/Spiral Rule (1994)
- stephencanon 2mo agoThis 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 https://cseweb.ucsd.edu/~gbournou/CSE131/rt_lt.rule.html).
- mananaysiempre 2mo agoWhy 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.)
- stephencanon 2mo agoThat’s exactly the same rule with more words (postfix = right, prefix = left).
- mananaysiempre 2mo agoI mean, yes, you’ve given a correct algorithm for parsing this simple precedence grammar, but it still leaves the impression that there’s something new to learn in addition to C expressions, whereas the whole point of the declaration syntax is that there largely isn’t.
- jcranmer 2mo agoThe 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.)
- Joker_vD 2mo ago> "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...
- HarHarVeryFunny 2mo agoYeah, Pascal is very readable, and ^Char is nicer than char* even if inconsistently concise (might expect "pointer to Char").
- xigoi 2mo agoOr even var str: array[10, ptr char]
- Joker_vD 2mo agoEh, or that, I guess. I personally would rather not squish the type of the elements together with the dimensions, but it can work like that, sure. Especially if you use square brackets for generic instantiation, so your example is semantically close to std::array<char*, 10> of C++. But if the language uses the square brackets (almost) exclusively for array indexing, then having simply "[10]" prefix as a shorthand for "array of 10 of ..." à la Zig/Go is quite reasonable. Then again, Go does use square brackets both for instantiation of generics and for array-related operations so... I guess they had the syntax of map types as a precedent.
- xigoi 2mo ago> Especially if you use square brackets for generic instantiation, so your example is semantically close to std::array<char*, 10> of C++. Yes, it is like that.
- pcfwik 2mo agoBeing 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 https://eigenstate.org/notes/c-decl
- nitrix 2mo agoSome more examples: 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 2mo agoThe way I've learned to read it is 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`.
- kps 2mo ago> int *w; But some misguided style guides demand the misleading `int* w;`, and then act surprised by `int* w, x`;
- AlienRobot 2mo agoThis is why I like python. str is a duck.
- classified 2mo agoThis is useful if you're far from the internet. Here's a web site that translates the gibberish to English or vice versa: https://cdecl.org/ https://cdecl.org/ Or apt install cdecl on Linux.
- bigfishrunning 2mo ago[dead]
- gnramires 2mo agoI'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".
- dnautics 2mo agoi find Zig does it fairly well. there is also no ambiguity between pointer and multiply.
- Joker_vD 2mo agoYeah, they took it from Pascal: 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]".
- dnautics 2mo agowell pascal doesnt use ^ for multiplication
- kazinator 2mo agoThere 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.
- tancop 2mo agoMy 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))`.
- bpavuk 2mo ago> My ideal syntax for function pointers is 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 syntax there 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()`
- WalterBright 2mo agoD simply reads right to left: int[]* p; // pointer to array of int int function(char*) fp; // pointer to function with char* parameter returning an int
- leni536 2mo agoThe spiral rule makes no sense, I never got why it got popular.
- dang 2mo agoRelated. Others? C "clockwise/spiral" rule to understand declarations - https://news.ycombinator.com/item?id=42564861 https://news.ycombinator.com/item?id=42564861 - Jan 2025 (75 comments) Clockwise/Spiral Rule - https://news.ycombinator.com/item?id=25494219 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 https://news.ycombinator.com/item?id=12775735 - Oct 2016 (68 comments) The “Clockwise/Spiral Rule” - https://news.ycombinator.com/item?id=8648287 https://news.ycombinator.com/item?id=8648287 - Nov 2014 (26 comments) The Clockwise/Spiral Rule - https://news.ycombinator.com/item?id=6471202 https://news.ycombinator.com/item?id=6471202 - Sept 2013 (7 comments) The "Clockwise/Spiral Rule" in C - https://news.ycombinator.com/item?id=5079787 https://news.ycombinator.com/item?id=5079787 - Jan 2013 (45 comments)
- 111101111 2mo ago[dead]
- seanhunter 2mo agoLearning this rule will mean you miss out on the opportunity to use one of the best words in the English language, which is “boustrophedonically” (meaning alternating left to right with right to left “as an ox ploughs a field”[1]). C is parsed boustrophedonically. [1] This is apparently the ancient Greek origin of the word.
- mistivia 2mo agoI think that in 2026, it's not a good idea to still be using human brainpower to parse gibberish that was hastily decided in the 1960s/1970s for the convenience of writing parsers. Just use tools like cdecl: https://manpages.ubuntu.com/manpages/focal/man1/cdecl.1.html https://manpages.ubuntu.com/manpages/focal/man1/cdecl.1.html