A pointer stores an address used to reach another object. There are two distinct objects to track: the pointer variable, which holds the address, and the target, which holds a value. Changing one does not necessarily change the other.
Read CS1: addresses, aliases, and lifetime before this lecture if addresses are new. Draw one box for each integer and another box for each pointer. An arrow from a pointer to its target is often clearer than writing a real machine address.
Obtain an address, then access its target #
The address-of operator & obtains an object's address. In an expression, the dereference operator * follows a pointer to access the object at that address.
int number = 7;
int* pointer = &number;
*pointer = 12; // number now contains 12The first line creates number and stores seven. The second creates pointer and stores number's address. It does not create another integer containing seven. The third line follows the stored address and writes twelve into number. The pointer still contains the same address; the value in its target has changed.
The expression pointer asks for the stored address. The expression *pointer accesses the integer at that address. The expression &pointer asks for the address of the pointer variable itself. These answer three different questions, so printing them should not be expected to produce the same kind of information.
Copying pointer into a second pointer copies the address: both arrows lead to number. Copying the dereferenced integer into a separate integer creates a value copy. A later change to number affects what both pointers read, but it does not update that independent copy.
The star in a declaration has a different job #
In int* p, q;, the star makes p a pointer to int. It does not also make q a pointer; q is an ordinary integer. The pointer marker belongs to an individual declarator. One variable per declaration can make this easier to read.
The type int* tells the compiler what kind of object the pointer is intended to access. It is not permission to treat an arbitrary address as an integer. Before dereferencing, the pointer must designate a live object of the appropriate type.
An array is also not a pointer variable. In many expressions, the array name converts to a pointer to its first element. For int values[4];, values commonly supplies that first-element address. But the array remains a four-element object. &values has pointer-to-array type: it points at the complete array rather than at one integer. The starting locations can coincide while their types and arithmetic differ.
Pointer arithmetic moves by elements #
Within a valid array, values[i] accesses the same element as *(values + i). Adding one to an integer-element pointer advances to the next integer, not merely the next byte. The element type supplies the step size.[1]
For four elements, pointers to indexes zero through three can be dereferenced. The one-past-end pointer is permitted as an end marker but cannot be dereferenced. A pointer to one ordinary integer does not become a four-element array simply because arithmetic can be written on it. Bounds come from the actual object, not from a desired loop length.
Allocate an object with a separate lifetime #
new int{42} allocates an integer initialized to forty-two and returns its address. The allocated integer's lifetime is not automatically tied to the block holding the pointer variable.
int* value = new int{42};
*value += 1;
delete value;
value = nullptr;The first line creates the allocation and records its address. The second dereferences that address and increases the stored integer to forty-three. The third line, delete, destroys the allocated integer and releases its storage. Afterward, value still contains the old address until the fourth line replaces it with nullptr.
A null pointer points to no object. It can be compared or tested, but it cannot be used to read an integer through dereferencing. Clearing one pointer also does not clear another pointer that copied the original address earlier. Such an alias still contains a stale address after deletion.
Initialization matters as well. new int supplies no meaningful initialized integer value to read. Use an initializer such as new int{0}, or assign a value before reading the allocated object. Do not delete an ordinary local object's address: delete must match storage obtained from the appropriate new expression.
Ownership is a responsibility, not a pointer shape #
The owner is the code responsible for releasing an allocation exactly once. Losing the last route to an unreleased allocation can leak memory. Reading through a pointer after its target has been deleted uses a dangling pointer. Deleting the same allocation twice is also invalid.
The exercises use raw pointers to expose these relationships. Ordinary C++ applications often use containers or smart pointers to connect cleanup to an owning object's lifetime. A raw pointer can still serve as a temporary, non-owning route to an existing object; receiving an address does not automatically confer ownership.
Practice and explanation #
After two pointers are made to refer to number, writing twelve through either changes the one shared integer. Reassigning one pointer to null leaves the other arrow unchanged. If number is still alive, the other pointer can still access it.
Now imagine the shared target was dynamically allocated and deleted. Does clearing only the owner make the second pointer safe? No. The target no longer exists, and the remaining address must not be dereferenced. Track object lifetime separately from pointer contents.