I was just confused about the following declaration in C:
I understand the
char *p as an array of character pointers, but this one is puzzling me. Based on the precedence of
*, how can I interpret this?
Software Engineering Stack Exchange is a question and answer site for professionals, academics, and students working within the systems development life cycle. It only takes a minute to sign up.Sign up to join this community
In 'C', an "array" is effectively the address of its first element ...
char a == char * a
... so your variable ...
... 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"
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
Adding a dimension (
int x) 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
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 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 
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
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.
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; size_t x_elements = sizeof x / sizeof *x
One other note: you ask about the relative precedences of
. 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.