points by stephencanon 13 years ago

The "spiral rule" makes for pretty pictures, but unfortunately it isn't correct. One simple example:

    int *foo[3][4];

The spiral rule would have us read this as "foo is an array [3] of pointers to array [4] of int". What it actually is is "foo is an array [3] of arrays [4] of pointers to int".

The correct rule* for parsing declarations is "Start from the thing being declared, and read right until you hit a closing parenthesis, then read left until you reach the matching paren, then go back to reading right, and always skip over any tokens you've already read." This is sometimes called the "right-left rule"; it's not as pretty as "spiral", but it actually works.

(*) the really correct rule is "follow the syntax and semantics specified in the language standard", but for some reason that confuses people.

tedunangst 13 years ago

I think it works if you treat all the [] as a single token. foo is a 3x4 two dimensional array of pointers to int.

  • stephencanon 13 years ago

    You can construct a rule along those lines that would work (though just taking all the [] at once doesn't suffice), but it ends up being precisely equivalent to the right-left rule, so I don't really see the point.

    • tedunangst 13 years ago

      I think the point is that the spiral rule is just a way to remember/implement the right left rule.

  • revelation 13 years ago

    But its not, there are no two dimensional arrays. There are arrays of arrays.

    • kzrdude 13 years ago

      Yes, but does it matter? We can call that a 2D array.

    • tedunangst 13 years ago

      The sizeof operator begs to differ.

      • sltkr 13 years ago

        How does it differ? It just gives the size of the whole object -- that doesn't mean it's not an array of arrays...

        • tedunangst 13 years ago

          Hmm, I'm not sure what to say. A two dimensional array is an array of arrays. I'm objecting to the idea that two dimensional arrays are some mythical construction.

          For instance, the only way to construct a ragged array is with pointers, and then sizeof doesn't "work" to give the complete size. Also, the [][] syntax is one of the places where pointers and arrays are less equivalent than usual. You can't pass a [][] array via star star.

        • jwhite 13 years ago

          To elaborate on what tedunangst said, for those less experienced with C:

          To some extent it's a matter of nomenclature, but one can draw a distinction in C between arrays of arrays and 2D arrays. It does matter. The following is an array of arrays of ints (a "ragged array" as tedunangst mentioned), if I malloc an array of pointers to ints and then add a loop that mallocs an array of ints for each a[i]:

            int **a;
          
            a = malloc(N * sizeof(a[0]));
            int i;
            for(i = 0; i < N; i++)
            {
                a[i] = malloc(M * sizeof(a[i][0]));
            }
          

          Then a[i][j] picks out a single int from the array of arrays. The layout in memory is then like this:

                +--+--+--+-       -+--+
            a:  |  |  |  |   ...   |  |   (array of pointers to arrays of ints)
                +--+--+--+-       -+--+
                  |   |
                  |   |
                  |  +--+--+--+-       -+--+
            a[0]: |->| 1| 2| 3|   ...   |  |  (array of ints)
                     +--+--+--+-       -+--+
                      |
                      |  +--+--+--+-       -+--+
            a[1]:     |->|41|42|43|   ...   |  |  (array of ints)
                         +--+--+--+-       -+--+
              .
              .	
              .
          

          Note that you cannot rely on these arrays to have any relationship with each other in respect to their positions in memory. That's entirely up to malloc(3).

          However, the following is a 2D array of ints:

            int a[3][4];
          

          The same syntax as before can be used to pick out one of the ints in the 2D array: a[i][j]. But this time the C standard gives us that this object is laid out in memory in row-major order. So if you initialize it like this:

            int a[3][4] = { {1, 2, 3, 4}, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
          

          then its memory layout looks like this:

                +--+--+--+--+--+--+--+--+--+--+--+--+
            a:  | 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
                +--+--+--+--+--+--+--+--+--+--+--+--+
                  ^        ^  ^        ^  ^        ^
                  |__a[0]__|  |__a[1]__|  |__a[2]__|
          

          This means you can index it manually like this:

            int x = *((int*)a + i * M + j);
          

          where M is the length of each row, in this case M==4. You can't do that with the ragged array-of-arrays.

          Exercise: try taking sizeof(a) in both cases, as suggested in the grandparent.

          Exercise: 3D arrays?

ralph 13 years ago

Yes, the Right-Left rule is the one to use; I first read of it in Anderson & Anderson's _Advanced C: Tips & Techniques_. Having that simple rule, there's no need to struggle over qsort()'s declaration and others, or create unnecessary, one-use, typedefs.