from Hacker News

The Clockwise/Spiral Rule of C declarations

by xvirk on 10/23/16, 10:27 PM with 68 comments

  • by stephencanon on 10/23/16, 11:05 PM

    This has been posted before[1], and the "spiral rule" is a load of hooey.

    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"[2]).

    The "spiral rule" dances around the truth without actually being precise enough to be useful.

    [1] https://news.ycombinator.com/item?id=5079787 [2] http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html

  • by colanderman on 10/23/16, 11:40 PM

    Way simpler: from inside out, read any subpart of the type as an expression. (Arrays have precedence over pointers, as usual.) The type that remains is that expression's type. So e.g. given the type:

        const char *foo[][50]
    
    the following expressions have the following types:

         foo       -> const char *[][50]
         foo[0]    -> const char *  [50]
         foo[0][0] -> const char *
        *foo[0][0] -> const char
    
    Another example:

        int (*const bar)[restrict]
        
          bar     -> int (*const)[restrict]
         *bar     -> int         [restrict]
        (*bar)[0] -> int
    
    One more:

        int (*(*f)(int))(void)
        
            f        -> int (*(*)(int))(void)
          (*f)       -> int (*   (int))(void)
          (*f)(0)    -> int (*        )(void)
         *(*f)(0)    -> int            (void)
        (*(*f)(0))() -> int
    
    (In this case, the last expression is more readily written as f(0)(), since pointers to functions and functions are called using the same syntax.)
  • by kazinator on 10/24/16, 1:39 AM

    The real rule is that the type construction operators mirror the unary and postfix family of operators (declaration follows use). For instance unary * declares a pointer, mimicking the dereference operator, and postfix [] and () declare functions, mimicking array indexing and function call.

    To follow the declaration you make use of the fact that postfix operators in have a higher precedence than unary, and that of course unary operators are right-associative, whereas postfix are left-associative (necessarily so, since both have to "bind" with their operand).

    So given

       int ***p[3][4][5];
    
    we follow the higher precedence, in right to left associativity: [3], [4], [5]. Then we run out of that, and follow the lower-precedence * * * in right-to-left order.

    If there are parentheses present, they split this process. We go through the postfixes, and then the unaries within the parens. Then we do the same outside those parens (perhaps inside the next level of parens):

       int ****(***p[3][4][5])[6][7];
                   1 2  3  4
                765
                               8  9
           1111
           3210
    
    Start at p, follow postfixes, then unaries within parens. Then the postfixes outside the parens and remaining unaries.

    The result is in fact a spiral just from going root postfix unary out postfix unary out. We just don't have to focus on the spiral aspect of it.

  • by robertelder on 10/23/16, 10:59 PM

    I used to think of the spiral rule as being a good guide, but then a commenter on HN showed me otherwise:

    https://news.ycombinator.com/item?id=12053206

  • by rfw on 10/23/16, 11:02 PM

    The rule is misleading in cases like:

        int* arr[][10];
    
    Spiral rule would state "arr is an array of pointers to arrays of 10 ints", where actually it would be "arr is an array of array of 10 pointers to int".

    Instead, when you write declarations, do it from right-to-left, e.g.:

       char const* argv[];
    
    "argv is an array of pointers to constant characters"

    It doesn't help with reading, unfortunately.

  • by gtrubetskoy on 10/23/16, 11:55 PM

    Contrast the first example with Golang:

      str [10]*byte
    
    which reads exactly as it is declared: "str is an array of length 10 of pointers to byte" (byte is Go equivalent of C char (mostly)).
  • by fazkan on 10/24/16, 1:45 PM

    I read a paper somewhere from dennis ritchie, where he explained the development of C language, the pros and cons; and in there he mentioned that reading complex declarations is a problem in C, he said that if we had placed the * operator to the left of the type it was qualifying then it would have been easier to write and understand more complex declarations.

    (PS. Golang has the right idea, since its developed by the guys who contributed to C)...

  • by nine_k on 10/23/16, 11:15 PM

    What makes me wonder is why C ended up with such a syntax. That is, its contemporary, Pascal, has a very straightforward, unambiguous syntax.
  • by ajarmst on 10/24/16, 1:31 AM

    Seems overly complex. The way I learned it, and now teach, is to read the type backwards (int const * is 'pointer to const int') for const correctness, but anything that requires more complex parsing by a human should just be typedef'd into submission.
  • by jacquesm on 10/24/16, 12:15 PM

    One problem with the web is that it will remember wrong information just as well as it will remember correct information.
  • by generic_user on 10/24/16, 1:13 AM

    While cdecl was probably written before some of you where born it still does precisely one thing and does it well.

    Cdecl (and c++decl) is a program for encoding and decoding C (or C++) type declarations.

    http://linuxcommand.org/man_pages/cdecl1.html

  • by sugarfactory on 10/24/16, 1:04 PM

    Once I tried to figure out how to parse complex C declarations just by reading the specification (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf), that is, without consulting guides for layman like this. But I gave up. I looked at what seemed a BNF-like description of the C grammar but I had no idea what it tells about the parsing rules. So I ended up using this guide: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html With this I managed to implement an imitation of cdecl.
  • by Chinjut on 10/24/16, 3:39 AM

    What is the value of bending ourselves to fit a confusingly designed old language, rather than bending the language to fit us? The very fact that articles like this have to be written indicates a failure of user interface design, which we needn't forever perpetuate.
  • by lisper on 10/23/16, 11:52 PM

    Things break down utterly in the presence of typedefs. What is this?

        foo(*baz(bing,boff(*bratz)(biff)))(buff);
  • by hilop on 10/24/16, 3:26 PM

    The first red flag is that the rule says "clockwise" where there s clearly to way to distinguish clocwise from anticlockwise inside the code. Only the completely arbitrary choice of up/down direction of the drawing affects clockwiseness.

    It's been 20years(!) Why is this incorrect advise still up at c-faq?

  • by mrcactu5 on 10/24/16, 2:40 AM

    symbols with equal amount of open and close parentheses in order are counted by Catalan numbers

        (())()(()())(())
    
    these count different arrangement of parentheses for function application. this guy is describing something like contour integration for computer programs
  • by faehnrich on 10/24/16, 1:31 PM

    Or, as the book Expert C Programming says, declarations in C are read boustrophedonically.
  • by Buge on 10/24/16, 1:06 AM

    I always heard of the right-left rule, which seems simpler and more accurate to me.

    http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html

  • by mtrycz on 10/24/16, 8:33 AM

    I'd take the striking simplicity of a lisp anyday compared to this mess.

    Yeah, I know, I'm not good enough, I didn't study enough, I'm not enlightened enough. But why make things so overly comples in the first place?

  • by marcv81 on 10/24/16, 11:37 AM

    This is the reason why golang declares the identifier before the type and the return value at the after the function parameters. This allows parsing any declaration from left to right.
  • by xyzzy4 on 10/24/16, 12:04 AM

    Just don't make complex declarations in C, it's almost never useful and won't help anyone out. It'll confuse people and make your code write-only. Just put in a couple extra lines of code somewhere if you have to. It won't be the end of the world.