4.3. Pointer Operators

Time: 00:05:00 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)

Pointers allow us to work more directly with a computer's memory. Programmers generally don't know (and have no control over) where the compiler and the operating system place variables in memory, so we often think about pointers in general terms. Specifically, when we draw pictures of pointers, we often replace a specific address (a pointer variable's contents) with an arrow: The arrow points to the data whose address the pointer stores. Although we can't control a variable's placement in memory, we can find its address with one of the pointer operators and save it in a pointer variable. Once we have the variable's address, we can work with it indirectly through the pointer.

Although we can use many operators with pointers, six are common. C++ forms two of them as complete words: new and delete; one operator consists of two characters without any spaces: ->, and the remaining three operators consist of a single character. Unfortunately, there are a limited number of characters on a keyboard, and all the symbols used to implement the single-character pointer operators have other overloaded meanings. The compiler distinguishes between the meanings based on context (i.e., where the symbols appear in a program). For example, the ampersand, &, forms the address of operator. Recall from the previous chapter that two adjacent ampersands form the logical-AND operator. Furthermore, the chapter 2 supplemental section introduces the single ampersand operator as the bitwise-AND operator. To make matters worse, besides its use as the multiplication operator, the asterisk, *, has two uses just within the context of pointers. First, it defines a pointer variable, and second, it serves as the dereference or the indirection operator (both name the same operation), which we take up in greater detail in the next section.

* When used in a variable definition, defines a pointer variable
* When used in an expression, is the dereference operator (also known as the indirection operator)
& The address of operator
Three pointer operators and their contextual meaning. The compiler can distinguish which operator each character represents by its context (i.e., where it appears in a statement), which implies that programmers must also learn how to distinguish between operators based on their context.

The following figure demonstrates two operations with pointers. It illustrates defining a pointer variable, p, and how to find the address of an existing variable, i. The address-of-operator may be used with any variable regardless of its type. The demonstration uses the integer variable i for simplicity. Subsequent sections in this chapter describe the other operators.

(a)
int	i;
int*	p;
An abstract illustration of what takes place in memory as two variables, i and p, are defined. The picture represents each variable with a rectangle, labeled with the variable name on the left side, an arbitrary memory address on the right side, and the contents (currently undefined) on the inside.
(b)
i = 123;
p = &i;
An abstract illustration of what takes place in memory as two variables, i and p, are initialized. The picture illustrates the operation i = 123 by writing 123 inside the rectangle representing i. 
The picture illustrates i's arbitrary address as 0x0a000014, calculated with the expression <kbd>&i</kbd>. The picture represents saving i's address in p by writing 0x0a000014 inside the rectangle representing p.
(c)   In this alternative view of a pointer, the value 0x0a000010 written inside the rectangle representing p is replaced with an arrow from p pointing to the rectangle representing variable i.
Understanding pointer operator behavior.
  1. Defines variables i and p. In this context, the asterisk defines p as a pointer variable, specifically, a variable that holds the address of an integer. The compiler allocates memory to hold the variables and maps their names to those memory locations. Both variables have an address, but the contents are undefined. When defining a pointer variable, there may be a space on either or both sides of the asterisk: int* p;, int *p;, or int * p;.
  2. Initializes both variables. The content of the variables changes, but their addresses remain the same. The ampersand is the address-of operator; it forms an expression with the variable immediately to its right that calculates the address of the variable. p and &i are assignment compatible because both sides of the assignment operator represent an address; that is, the assignment operation is possible because pointers are variables that store addresses.
  3. An alternate view of (b): For the sake of illustrating what is taking place in memory, the address stored in p is replaced by an arrow pointing from p to i. When using a picture to understand or solve a problem that uses pointers, it is very common to represent the value stored in a pointer variable as an arrow from the pointer variable to the data it points to or refers to.

Many of the C++ operators introduced in previous chapters will "work" with pointers in the sense that they will compile and run without errors. However, not all operators produce meaningful or useful results when applied to pointers. If you recall how pointers "look," you can get a good sense of how or if most operators work with them.

T data;
T* p = &data;
-----------------
T* p = new T;
An abstract representation of pointer variable p pointing to data - an arrow from p to the data.
(a)(b)
T* q = p;
Variable p continues to point to the data while a new pointer variable, q, also points to the data.
(c)(d)
sizeof(p)
T denotes a general data type, and p is a pointer to a T object. The picture demonstrates that even as the sizeof(T) increases, the sizeof(p) does not. That is, the size of a pointer is constant and unrelated to the size of the data it points to.
(e)(f)
Pointers and general operators. For this discussion, we'll call operators not specifically intended to work with pointers general operators. It's easier to understand how general operators behave when applied to pointers if we consider the relationship between the pointer and the data it points to. For generality, the illustration uses "T" to represent an unspecified or varying data type.
  1. A program can define and initialize a pointer in two ways.
  2. An abstract representation of the relation between a pointer and the data it points to.
  3. Assignment: pointer variables store the address of some data. When a statement operation assigns one pointer to another, it copies the address saved in the right-hand pointer to the left-hand pointer.
  4. Pointer assignment results in two pointers pointing to the same data. The assignment operation does not copy the data.
  5. sizeof forms an expression that is the size, measured in bytes, of the argument. When applied to a pointer variable, it returns the size of the pointer, not the size of the data to which it points.
  6. Variables or data have an address and content. While the size of data varies with its type, the size of addresses are constant - they don't change - and are independent of the data size to which they point. So, when a program defines pointers to point to data, the sizes of the pointers are the same, but the data sizes depend on their type. Although the sizes of some data types differ between hardware platforms and with compiler settings, some relationships are universal:
    • sizeof(p) > sizeof(char)
    • sizeof(p) = sizeof(int)
    • sizeof(p) < sizeof(big_class)

We have, so far, only focused on the behavior of the pointer operators without considering why we might want to use them in a real-world program. We take this approach because pointers are a new concept to most of you, and I believe it is easier to introduce them without the distraction of a problem. However, it is challenging to appreciate the value of pointers without demonstrating their use. We will see some legitimate uses for pointers later in this chapter and throughout the text. But, before we see those examples, we must explore the indirection or dereference operator, the most challenging pointer operator to understand.