2.3.2. Pointers

Image

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.


Image Warning

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;  // both ip1 and ip2 are pointers to int
double dp, *dp2; // dp2 is a pointer to double; dp is a double

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; // p holds the address of ival; p is a pointer to 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;  // ok: initializer is the address of a double
double *pd2 = pd;    // ok: initializer is a pointer to double
int *pi = pd;  // error: types of pi and pd differ
pi = &dval;    // error: assigning the address of a double to a pointer to int

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; // p holds the address of ival; p is a pointer to ival
cout << *p;     // * yields the object to which p points; prints 42

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;     // * yields the object; we assign a new value to ival through p
cout << *p; // prints 0

When we assign to *p, we are assigning to the object to which p points.


Image Note

We may dereference only a valid pointer that points to an object.


Null Pointers

A null pointer does not point to any object. Code can check whether a pointer is null before attempting to use it. There are several ways to obtain a null pointer:

int *p1 = nullptr; // equivalent to int *p1 = 0;
int *p2 = 0;       // directly initializes p2 from the literal constant 0
// must #include cstdlib
int *p3 = NULL;    // equivalent to int *p3 = 0;

Image

The most direct approach is to initialize the pointer using the literal nullptr , which was introduced by the new standard. nullptr is a literal that has a special type that can be converted (§ 2.1.2, p. 35) to any other pointer type. Alternatively, we can initialize a pointer to the literal 0, as we do in the definition of p2.

Older programs sometimes use a preprocessor variable named NULL, which the cstdlib header defines as 0.

We’ll describe the preprocessor in a bit more detail in § 2.6.3 (p. 77). What’s useful to know now is that the preprocessor is a program that runs before the compiler. Preprocessor variables are managed by the preprocessor, and are not part of the std namespace. As a result, we refer to them directly without the std:: prefix.

When we use a preprocessor variable, the preprocessor automatically replaces the variable by its value. Hence, initializing a pointer to NULL is equivalent to initializing it to 0. ModernC++ programs generally should avoid using NULL and use nullptr instead.

It is illegal to assign an int variable to a pointer, even if the variable’s value happens to be 0.

int zero = 0;
pi = zero;        // error: cannot assign an int to a pointer

Assignment and Pointers

Both pointers and references give indirect access to other objects. However, there are important differences in how they do so. The most important is that a reference is not an object. Once we have defined a reference, there is no way to make that reference refer to a different object. When we use a reference, we always get the object to which the reference was initially bound.

There is no such identity between a pointer and the address that it holds. As with any other (nonreference) variable, when we assign to a pointer, we give the pointer itself a new value. Assignment makes the pointer point to a different object:

int i = 42;
int *pi = 0;   // pi is initialized but addresses no object
int *pi2 = &i; // pi2 initialized to hold the address of i
int *pi3;      // if pi3 is defined inside a block, pi3 is uninitialized
pi3 = pi2;        // pi3 and pi2 address the same object, e.g., i
pi2 = 0;          // pi2 now addresses no object

It can be hard to keep straight whether an assignment changes the pointer or the object to which the pointer points. The important thing to keep in mind is that assignment changes its left-hand operand. When we write

pi = &ival; //  value in pi is changed; pi now points to ival

we assign a new value to pi, which changes the address that pi holds. On the other hand, when we write

*pi = 0;    //  value in ival is changed; pi is unchanged

then *pi (i.e., the value to which pi points) is changed.

Other Pointer Operations

So long as the pointer has a valid value, we can use a pointer in a condition. Just as when we use an arithmetic value in a condition (§ 2.1.2, p. 35), if the pointer is 0, then the condition is false:

int ival = 1024;
int *pi = 0;      // pi is a valid, null pointer
int *pi2 = &ival; // pi2 is a valid pointer that holds the address of ival
if (pi)  // pi has value 0, so condition evaluates as false
    // ...
if (pi2) // pi2 points to ival, so it is not 0; the condition evaluates as true
    // ...

Any nonzero pointer evaluates as true

Given two valid pointers of the same type, we can compare them using the equality (==) or inequality (!=) operators. The result of these operators has type bool. Two pointers are equal if they hold the same address and unequal otherwise. Two pointers hold the same address (i.e., are equal) if they are both null, if they address the same object, or if they are both pointers one past the same object. Note that it is possible for a pointer to an object and a pointer one past the end of a different object to hold the same address. Such pointers will compare equal.

Because these operations use the value of the pointer, a pointer used in a condition or in a comparsion must be a valid pointer. Using an invalid pointer as a condition or in a comparison is undefined.

§ 3.5.3 (p. 117) will cover additional pointer operations.

void* Pointers

The type void* is a special pointer type that can hold the address of any object. Like any other pointer, a void* pointer holds an address, but the type of the object at that address is unknown:

double obj = 3.14, *pd = &obj;
// ok: void* can hold the address value of any data pointer type
void *pv = &obj;  // obj can be an object of any type
pv = pd;          // pv can hold a pointer to any type

There are only a limited number of things we can do with a void* pointer: We can compare it to another pointer, we can pass it to or return it from a function, and we can assign it to another void* pointer. We cannot use a void* to operate on the object it addresses—we don’t know that object’s type, and the type determines what operations we can perform on the object.

Generally, we use a void* pointer to deal with memory as memory, rather than using the pointer to access the object stored in that memory. We’ll cover using void* pointers in this way in § 19.1.1 (p. 821). § 4.11.3 (p. 163) will show how we can retrieve the address stored in a void* pointer.


Exercises Section 2.3.2

Exercise 2.18: Write code to change the value of a pointer. Write code to change the value to which the pointer points.

Exercise 2.19: Explain the key differences between pointers and references.

Exercise 2.20: What does the following program do?

int i = 42;
int *p1 = &i;
*p1 = *p1 * *p1;

Exercise 2.21: Explain each of the following definitions. Indicate whether any are illegal and, if so, why.

int i = 0;

(a) double* dp = &i;

(b) int *ip = i;

(c) int *p = &i;

Exercise 2.22: Assuming p is a pointer to int, explain the following code:

if (p) // ...
if (*p) // ...

Exercise 2.23: Given a pointer p, can you determine whether p points to a valid object? If so, how? If not, why not?

Exercise 2.24: Why is the initialization of p legal but that of lp illegal?

int i = 42;    void *p = &i;     long *lp = &i;


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.144.228