1

I was just confused about the following declaration in C:

char **p[5]

I understand the char *p[] as an array of character pointers, but this one is puzzling me. Based on the precedence of [] over *, how can I interpret this?

closed as off-topic by gnat, Ixrec, Bart van Ingen Schenau, user22815, user40980 Jan 22 '16 at 3:39

  • This question does not appear to be about software engineering within the scope defined in the help center.
If this question can be reworded to fit the rules in the help center, please edit the question.

  • 8
  • That deserves to be posted as the answer. It also deserves mega up-votes for introducing us to that site. – Mawg Jan 14 '16 at 13:12
  • @mawg: Cdecl.org is great, but answers that are just links don't make good answers, which is why Bart made it a comment. – Blrfl Jan 14 '16 at 15:15
  • 1
    I see what you say. But it's not just a link - "declare p as array 5 of pointer to pointer to char" is a valid answer. it just lacks the pretty ascii art ;-) – Mawg Jan 14 '16 at 15:29
  • Use the Clockwise/Spiral Rule approach to parse the declaration as well. – lifebalance Jan 15 '16 at 9:19
4

In 'C', an "array" is effectively the address of its first element ...

char a[5] == char * a 

... so your variable ...

char **p[5]

... is a pointer to a pointer to a pointer to a character (of which there just happen to be five allocated sequentially in memory).

If in doubt, revert to pen and paper and draw boxes to represent each pointer:

  ---      ---      --- 
p| * | -> | * | -> | * | 
  ---      ---      --- 
                     |
                     v 
                    "four\0" 
  • You sure it's not an array of five pointers to pointers to characters? – Blrfl Jan 14 '16 at 14:20
  • Never confuse an array and a pointer. Not even in passing. – Deduplicator Jan 14 '16 at 15:05
4

Variables in C are declared as a type followed by an identifier followed by an optional dimension. (I'm leaving out optional modifiers, like static because they aren't germane.) I'll start with integers since they're easy to digest:

If there's no dimension present, then the variable is of the type immediately to the identifier's left. Declaring int x means that x is of type int. Same with a pointer: int *x is of type int *.

Adding a dimension (int x[5]) changes the declaration into an array, which has some side effects:

  • The array itself (i.e., its contents) is the variable; the identifier just becomes a way to refer to it, and in C that disqualifies it from being used as an lvalue. This is a holdover from assembly language, where you could define a block of space in the source, give it a label and have that label's final location filled in everywhere it was used during linking. You couldn't change the label; if you wanted a variable, it had to be in a register or memory.

  • The compiler will allocate enough space to hold the array (be it on the stack for an automatic, space in the object for a static or as part of a structure) and make a note of where that space is.

  • Use of the array's identifier as an rvalue results in it being treated as if it were a pointer to the first item* in all but two contexts (those being the & and sizeof operators). For purposes of understanding what's been declared, treat it as a pointer: add a star to the end of the type and don't mind the dimension (e.g., int x[5] behaves like int *x when x is used as an rvalue).

Applying that to your example, we break it into its constituent parts:

char **    p    [5]

A dimension is present, so we chop it off and add a star to the type:

char ***   p

Used as an rvalue in all but the exceptional cases, p is the equivalent to a pointer to a pointer a pointer to char.

The problem with pointers is that they don't have any notion of where they can be pointed without exceeding array bounds. This is another holdover from assembly, where an address might be sitting in a register with no other context and might not point at an array at all; it could just be some random instance of that type.

The sizeof operator gives you a way to determine the size of an array, because its operand is one of those exceptional cases where the identifier doesn't evaluate to a pointer:

int x[5];
size_t x_elements = sizeof x / sizeof *x

One other note: you ask about the relative precedences of * and []. In a declaration, they're not operators because you're not evaluating an expression. Ergo, precedence does not apply.


*At some point in the 30 years since I learned C, somebody started calling this decay. As a long-time assembly programmer, I think it's a terrible term because the implication is that the by-product is somehow inferior. There are, unfortunately, a lot of language purists who fail to understand that somebody has to write all of this low-level stuff to make their beautiful abstractions work. K&R made that quite clear in the preface to the first edition of The C Programming Language that this is the application for which C was developed.

Not the answer you're looking for? Browse other questions tagged or ask your own question.