Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The spiral rule is wrong. It breaks for things as simple as arrays of arrays.

    //        +---------+
    //        |   +--+  |
    //        |   ^  |  |
        int /*|*/ aa[2][3];
    //   ^    |      |  |
    //   |    +------+  |
    //   +--------------+
The correct result is "aa is a (2-element) array of a (3-element) array of ints".

To get the correct interpretation, you have to know that the spiral has to avoid the "int" element after passing through "[2]". This defeats the purpose of the spiral, since the line sometimes goes through the element and sometimes it doesn't. For example if it were "int * paa[2][3]" instead, the correct order is { [2]; [3];* ; int }. Note how the star is first avoided and comes after [3]. A 2-array of a 3-array of pointer to int.

How would you know when the spiral "avoids" the element on the left and when it doesn't? Well, you need to know the declaration grammar to know that [] and () bind stronger than the thing on the left, so you need to process those first. But if you know this, drawing a spiral is redundant, because you already parsed the thing.

I think the spiral rule is inherently wrong and should not be reposted as a helpful cheat-sheet for parsing C/C++ declaration syntax.



The correct rule is that postfix operators bind more tightly than unary. Declarations imitate use, so in declarators, we can regard the unary star and the postfix () and [] as unary and postfix operators. So "star star a [][]" is "star star (a [] [])".

To apply the spiral rule in these cases, we have to collapse together the chained unary and postfix operators so that the spiral traverses them as one clump:

      //        +--------+
      //        |        |
      //        |        |
          int  *** aa[2][3][4];
      //   ^    |   v    |
      //   |    |   |    |
      //   |    |   |    |
      //   +----+   +----+

"aa is an (array of array of array) of (pointer to pointer to pointer) to int".

The spiral only makes additional iterations when precedence parentheses are present. Each level of parentheses has its own postfix-unary round trip:

      //        +----------+
      //        | +----+   |
      //        | |    |   |
      //        | |    |   |
          int  **(* aa[2])[3][4];
      //   ^    | | v  |   |
      //   |    | | |  |   |
      //   |    | | |  |   |
      //   +----+ | +--+   |
      //          +--------+


A good exercise is to then guess why the spiral rule seems to work most of the time even though it is inherently wrong.

The reason is that some types that are syntactically valid are forbidden by C/C++. You cannot have a function returning a function. A function returning an array. An array of functions. You can only have a pointer to these (function returning a pointer to function/array or an array of pointer to functions), and then they need to be correctly parenthesized. Then the spiral rule works because you only have two elements in a parenthesis and you can just go right and then go left... unless you have arrays of arrays which are legal, and then it doesn't work.

But the more correct rule would be to go right and parse all [] and () inside the current parenthesis level, then go parse the * -s (including const/volatile) on the left. Then repeat for outer parenthesis levels.


Aha, so the final working algorithm is "the spiral rule but with a rightward detour on arrays of arrays."


The working algorithm is that the postfix operators in declarators (just like in expressions) have higher precedence than the unary ones (just like in expressions), but are overridable with parentheses (like in you know what).

The declared identifier is, first and foremost, the clump of high precedence postfix things that are on it:

     a[3][4][5];  // a is an array of array of array

     b(int);      // b is a function

     c(int)[3];   // c is an array of functions: nonexistent
Then we consider the unaries:

     *** whatever;  // whatever of pointer to pointer to pointer
Then the declaration specifiers:

     int whatever;   // whatever of/to int


Forget the spiral rule. That's putting an if-statement into a buggy algorithm instead of using the correct algorithm.

The simplest correct algorithm is to 1. From the identifier, go right one by one in the current parenthesis level and process every element. 2. From the identifier again, go left one by one in the current parenthesis level and process every element. 3. Repeat from step 1. except the "identifier" is the part you already processed.

Within step 1, you will encounter arrays and function parameter lists. Within step 2, you will encounter pointers (in C++ also references), const and volatile modifiers, and named types. Neither algorithm covers it, but if there is no identifier to start with, then you start at the most nested level between the elements that may occur in step 1 and 2 and if you find a comma, you were just processing the type of a function parameter.

Spiraling is unnecessary. Within step 1, you DON'T spiral because of arrays of arrays or you turn back because of a closing parenthesis so a spiral doesn't need to guide you. Within step 2, the elements on the right are all processed already, so a spiral going back right will hit nothing. For simple but common cases like "const int * ptr * const_ptr_to_const_int" you're spiraling around nothing on the right-hand side.

Let's rework and simplify the examples from the http://c-faq.com/decl/spiral.anderson.html page. Comments show what the result of parsing an element is. Process >> >> left to right and << << right to left.

         char * str [10];
    //          XXX >>>> "str is", "an array of 10..."
    //   <<<< < XXX      "pointer to", "char"
    
         char * ( * fp ) ( int, float * );
    //              XX                    "fp is"
    //            < XX                    "a pointer to"
    //          XXXXXXXX >>>>>>>>>>>>>>>> "a function taking (int, float*) and returning"
    //   <<<< < XXXXXXXX                  "a pointer to", "char"
    
         void (* signal (int, void (* fp) (int))) (int);
    //                              < XX                "fp is", "a pointer to"
    //                             XXXXXX >>>>>         "a function taking (int) and returning"
    //                        <<<< XXXXXX               "void", "and it is a function parameter to something else"
    //           XXXXXX >>>>>>>>>>>>>>>>>>>>>>>>        "signal is", "a function taking (int, void(*fp)(int)) and returning"
    //         < XXXXXX                                 "a pointer to"
    //        XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX >>>>> "a function taking (int) and returning"
    //   <<<< XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX       "void"
    
         const char * chptr;
    //   <<<<< <<<< < XXXXX "chptr is", "a pointer to", "char", "that is const"
    // Notice how a spiral would make this worse and the page omits drawing it for a reason
    
         char * const chptr;
    //   <<<< < <<<<< XXXXX "chptr is", "a constant", "pointer to", "char"

         volatile char * const chptr;
    //   <<<<<<<< <<<< < <<<<< XXXXX "chptr is", "a constant", "pointer to", "char", "that is volatile"
Notice that in the examples where Anderson drew any spirals, it only made sense because there was a single "<<"/">>" element to the left and to the right of the XXXX part. In all other cases, spirals don't make sense because you might have to avoid the element on the left or there is no element on the right to spiral through, you're just going right-to-left. Why fit a spiral when a straight-line arrow will do?




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: