2.3.3 Understanding Compound Type Declarations
As we’ve seen, a variable definition consists of a base type and a list of declarators. Each declarator can relate its variable to the base type differently from the other declarators in the same definition. Thus, a single definition might define variables of different types:
-
- int i = 1024, *p = &i, &r = i;
Many programmers are confused by the interaction between the base type and the type modification that may be part of a declarator.
Defining Multiple Variables
It is a common misconception to think that the type modifier (* or &) applies to all the variables defined in a single statement. Part of the problem arises because we can put whitespace between the type modifier and the name being declared:
- int* p;
We say that this definition might be misleading because it suggests that int* is the type of each variable declared in that statement. Despite appearances, the base type of this declaration is int, not int*. The * modifies the type of p. It says nothing about any other objects that might be declared in the same statement:
- int* p1, p2;
There are two common styles used to define multiple variables with pointer or reference type. The first places the type modifier adjacent to the identifier:
- int *p1, *p2;
This style emphasizes that the variable has the indicated compound type.
The second places the type modifier with the type but defines only one variable per statement:
- int* p1;
- int* p2;
This style emphasizes that the declaration defines a compound type.
There is no single right way to define pointers or references. The important thing is to choose a style and use it consistently.
In this book we use the first style and place the * (or the &) with the variable name.
Pointers to Pointers
In general, there are no limits to how many type modifiers can be applied to a declarator. When there is more than one modifier, they combine in ways that are logical but not always obvious. As one example, consider a pointer. A pointer is an object in memory, so like any object it has an address. Therefore, we can store the address of a pointer in another pointer.
We indicate each pointer level by its own *. That is, we write ** for a pointer to a pointer, *** for a pointer to a pointer to a pointer, and so on:
- int ival = 1024;
- int *pi = &ival;
- int **ppi = π
Here pi is a pointer to an int and ppi is a pointer to a pointer to an int. We might represent these objects as

Just as dereferencing a pointer to an int yields an int, dereferencing a pointer to a pointer yields a pointer. To access the underlying object, we must dereference the original pointer twice:
- cout << "The value of ival\n"
- << "direct value: " << ival << "\n"
- << "indirect value: " << *pi << "\n"
- << "doubly indirect value: " << **ppi
- << endl;
This program prints the value of ival three different ways: first, directly; then, through the pointer to int in pi; and finally, by dereferencing ppi twice to get to the underlying value in ival.
References to Pointers
A reference is not an object. Hence, we may not have a pointer to a reference. However, because a pointer is an object, we can define a reference to a pointer:
- int i = 42;
- int *p;
- int *&r = p;
- r = &i;
- *r = 0;
The easiest way to understand the type of r is to read the definition right to left. The symbol closest to the name of the variable (in this case the & in &r) is the one that has the most immediate effect on the variable’s type. Thus, we know that r is a reference. The rest of the declarator determines the type to which r refers. The next symbol, * in this case, says that the type r refers to is a pointer type. Finally, the base type of the declaration says that r is a reference to a pointer to an int.
It can be easier to understand complicated pointer or reference declarations if you read them from right to left.
EXERCISES SECTION 2.3.3
Exercise 2.25: Determine the types and values of each of the following variables.
(a) int* ip, &r = ip; (b) int i, *ip = 0; (c) int* ip, ip2;