2.3.2 Pointers
A pointer is a compound type that “points to” another type. Like references, pointers are used for indirect access to other objects. Unlike a reference, a pointer is an object in its own right. Pointers can be assigned and copied; a single pointer can point to several different objects over its lifetime. Unlike a reference, a pointer need not be initialized at the time it is defined. Like other built-in types, pointers defined at block scope have undefined value if they are not initialized.
Pointers are often hard to understand. Debugging problems due to pointer errors bedevil even experienced programmers.
We define a pointer type by writing a declarator of the form *d, where d is the name being defined. The * must be repeated for each pointer variable:
- int *ip1, *ip2;
- double dp, *dp2;
Taking the Address of an Object
A pointer holds the address of another object. We get the address of an object by usin the address-of operator (the & operator):
- int ival = 42;
- int *p = &ival;
The second statement defines p as a pointer to int and initializes p to point to the int object named ival. Because references are not objects, they don’t have addresses. Hence, we may not define a pointer to a reference.
With two exceptions, which we cover in § 2.4.2 (p. 62) and § 15.2.3 (p. 601), the types of the pointer and the object to which it points must match:
- double dval;
- double *pd = &dval;
- double *pd2 = pd;
- int *pi = pd;
- pi = &dval;
The types must match because the type of the pointer is used to infer the type of the object to which the pointer points. If a pointer addressed an object of another type, operations performed on the underlying object would fail.
Pointer Value
The value (i.e., the address) stored in a pointer can be in one of four states:
1. It can point to an object.
2. It can point to the location just immediately past the end of an object.
3. It can be a null pointer, indicating that it is not bound to any object.
4. It can be invalid; values other than the preceding three are invalid.
It is an error to copy or otherwise try to access the value of an invalid pointer. As when we use an uninitialized variable, this error is one that the compiler is unlikely to detect. The result of accessing an invalid pointer is undefined. Therefore, we must always know whether a given pointer is valid.
Although pointers in cases 2 and 3 are valid, there are limits on what we can do with such pointers. Because these pointers do not point to any object, we may not use them to access the (supposed) object to which the pointer points. If we do attempt to access an object through such pointers, the behavior is undefined.
Using a Pointer to Access an Object
When a pointer points to an object, we can use the dereference operator (the * operator) to access that object:
- int ival = 42;
- int *p = &ival;
- cout << *p;
Dereferencing a pointer yields the object to which the pointer points. We can assign to that object by assigning to the result of the dereference:
- *p = 0;
- cout << *p;
When we assign to *p, we are assigning to the object to which p points.
We may dereference only a valid pointer that points to an object.
KEY CONCEPT: SOME SYMBOLS HAVE MULTIPLE MEANINGS
Some symbols, such as & and *, are used as both an operator in an expression and as part of a declaration. The context in which a symbol is used determines what the symbol means:
- int i = 42;
- int &r = i;
- int *p;
- p = &i;
- *p = i;
- int &r2 = *p;
In declarations, & and * are used to form compound types. In expressions, these same symbols are used to denote an operator. Because the same symbol is used with very different meanings, it can be helpful to ignore appearances and think of them as if they were different symbols.