Declarations in C are expression-centric, meaning that the form of the declaration should match the form of the expression in executable code.
For example, suppose we have a pointer to an integer named p
. We want to access the integer value pointed to by p
, so we dereference the pointer, like so:
x = *p;
The type of the expression *p
is int
; therefore, the declaration of p
takes the form
int *p;
In this declaration, int
is the type specifier, and *p
is the declarator. The declarator introduces the name of the object being declared (p
), along with additional type information not provided by the type specifier. In this case, the additional type information is that p
is a pointer type. The declaration can be read as either "p
is of type pointer to int
" or "p
is a pointer to type int
". I prefer to use the second form, others prefer the first.
It's an accident of C and C++ syntax that you can write that declaration as either int *p;
or int* p;
. In both cases, it's parsed as int (*p);
-- in other words, the *
is always associated with the variable name, not the type specifier.
Now suppose we have an array of pointers to int
, and we want to access the value pointed to by the i'th element of the array. We subscript into the array and dereference the result, like so:
x = *ap[i]; // parsed as *(ap[i]), since subscript has higher precedence
// than dereference.
Again, the type of the expression *ap[i]
is int
, so the declaration of ap
is
int *ap[N];
where the declarator *ap[N]
signifies that ap
is an array of pointers to int
.
And just to drive the point home, now suppose we have a pointer to a pointer to int
and want to access that value. Again, we deference the pointer, then we dereference that result to get at the integer value:
x = **pp; // *pp deferences pp, then **pp dereferences the result of *pp
Since the type of the expression **pp
is int
, the declaration is
int **pp;
The declarator **pp
indicates that pp
is a pointer to another pointer to an int
.
Double indirection shows up a lot, typically when you want to modify a pointer value you're passing to a function, such as:
void openAndInit(FILE **p)
{
*p = fopen("AFile.txt", "r");
// do other stuff
}
int main(void)
{
FILE *f = NULL;
...
openAndInit(&f);
...
}
In this case, we want the function to update the value of f
; in order to do that, we must pass a pointer to f
. Since f
is already a pointer type (FILE *
), that means we are passing a pointer to a FILE *
, hence the declaration of p
as FILE **p
. Remember that the expression *p
in openAndInit
refers to the same object that the expression f
in main
does.
In both declarations and expressions, both []
and ()
have higher precedence than unary *
. For example, *ap[i]
is interpreted as *(ap[i])
; the expression ap[i]
is a pointer type, and the *
dereferences that pointer. Thus ap
is an array of pointers. If you want to declare a pointer to an array, you must explicitly group the *
with the array name, like so:
int (*pa)[N]; // pa is a pointer to an N-element array of int
and when you want to access a value in the array, you must deference pa
before applying the subscript:
x = (*pa)[i];
Similarly with functions:
int *f(); // f is a function that returns a pointer to int
...
x = *f(); // we must dereference the result of f() to get the int value
int (*f)(); // f is a pointer to a function that returns an int
...
x = (*f)(); // we must dereference f and execute the result to get the int value