Skip to main content

C++ Pointers

caution
Code SnippetWhat it does
int&Reference (l-value)
cout << &xprints memory address of a variable x
cout << *(&x)prints value at the memory address of variable x
int*pointer to an int valuea pointer is initialized with the address of a variable
cout << *ptrprints the value at ptr (same as *(&x) becoz &x == ptr)
Node*& indexNodeReference to a pointer

Before learning Pointers

The address-of operator - &x

returns data type: (pointer typer[int*]) or (pi i.e. pointer to int)

int main()
{
int x{ 5 };
std::cout << x << '\n'; // print the value of variable x
std::cout << &x << '\n'; // print the memory address of variable x

return 0;
}

we use the address-of operator (&) to retrieve the address assigned to variable x and print that address to the console. Memory addresses are typically printed as hexadecimal values, often without the *0x prefix like this 0x0027FEA0.

& symbol confusion in Reference and Address-Of

The & symbol tends to cause confusion because it has different meanings depending on context:

  • When following a type name, & denotes an lvalue reference: int& ref.
  • When used in a unary context in an expression, & is the address-of operator: cout << &x.
  • When used in a binary context in an expression, & is the Bitwise AND operator: cout << x & y.

The dereference operator * - *ptr *(&x)

The dereference operator (*) (also occasionally called the indirection operator) returns the value at a given memory address as an lvalue:

int main()
{
int x{ 5 };
std::cout << x << '\n'; // print the value of variable x
std::cout << &x << '\n'; // print the memory address of variable x

std::cout << *(&x) << '\n'; // print the value at the memory address of variable x (parentheses not required, but make it easier to read)

return 0;
}

Although the dereference operator looks just like the multiplication operator, you can distinguish them because the dereference operator is unary, whereas the multiplication operator is binary.

& vs *

The address-of operator (&) and dereference operator (*) work as opposites: address-of gets the address of an object, and dereference gets the object at an address.


Pointers

A pointer is an object that holds a memory address (typically of another variable) as its value. This allows us to store the address of some other object to use later.

note
  • When declaring a pointer type, place the asterisk next to the type name.
  • must always initialize your pointers to a known value

initialization of pointer

pointers are used to hold the address of another variable (which we can get using the address-of operator (&)).

Once we have a pointer holding the address of another object, we can then use the dereference operator (*) to access the value at that address. For example:

int x = 89
int* ptr = &x; /* `int*` is the pointer declaration, `&x` is the address of vaiable x */

ptr is holding the address of x, so we say that ptr is “pointing to” x.

int x = 89
int* ptr = &x; /* `int*` is the pointer declaration, `&x` is the address of vaiable x */
cout << *ptr; /* as ptr is address of x, `*ptr` gives value at address of ptr */

change variable ptr is pointing to

int x = 90;
int* ptr = &x; /* pointer holding address of 'x' */
ptr = &y; /* CHANGED: pointer holding address to 'y' */

use a pointer to change the value being pointed at

int x = 90;
int* ptr = &x; /* pointer holds address of 'x' */
*ptr = 11; /* `*ptr` means x */ // `*ptr` acts like '*(&x)' , so we can print or update it just like 'x'

Pointers int* vs Reference int&

both pointers and references provide a way to indirectly access another object.

Pointer int*Reference int&
we need to explicitly get the address to point at, and we have to explicitly dereference the pointer to get the value.the address-of and dereference happens implicitly.
Pointers can change what they are pointing at.References can NOT be reseated (changed to reference something else)
Pointers are objects.References are not objects

sizeof() of pointer

  • sizeof(ptr): A 32-bit machine means that pointers will be 32 bits in length, but sizeof() always prints the size in bytes. 32 bits is 4 bytes. Thus the sizeof(ptr) is 4.
  • sizeof(*ptr): *ptr means the value of a variable, so the sizeof(*ptr) will return the size of that variable's data type.
cout << sizeof(ptr) << '\n';
cout << sizeof(*ptr) << '\n';

address of a pointer int**``int***

int x = 89;
int* ptrx = &x; // address of `x`
int** ptr_of_ptrx = &ptrx; // address of `pointer of x`
int*** ptr_of_ptr_of_ptrx = &ptr_of_ptrx; // address of `pointer of (pointer of x)`

TO DO! -- nullptr & dangling pointers

  • convert dangling pointers to nullptr

  • always check for null ptr before dereferencing (*ptr)

// Assume ptr is some pointer that may or may not be a null pointer
if (ptr) // if ptr is not a null pointer
std::cout << *ptr << '\n'; // okay to dereference
else
// do something else that doesn't involve dereferencing ptr (print an error message, do nothing at all, etc...)

Pointer and const recap

To summarize, you only need to remember 4 rules, and they are pretty logical:

  • A non-const pointer can be assigned another address to change what it is pointing at
  • A const pointer always points to the same address, and this address can not be changed.
  • A pointer to a non-const value can change the value it is pointing to. These can not point to a const value.
  • A pointer to a const value treats the value as const when accessed through the pointer, and thus can not change the value it is pointing to. These can be

Keeping the declaration syntax straight can be a bit challenging:

The pointer's type defines the type of the object being pointed at. So a const in the type means the pointer is pointing at a const value. A const after the asterisk means the pointer itself is const and it can not be assigned a new address.

int main()
{
int value = 5;

int* ptr0 { &value }; // ptr0 points to an "int" and is not const itself, so this is a normal pointer.
const int* ptr1 { &value }; // ptr1 points to a "const int", but is not const itself, so this is a pointer to a const value. /* value CAN'T be changed, ptr address CAN change but only to a `const` variable */
int* const ptr2 { &value }; // ptr2 points to an "int", but is const itself, so this is a const pointer (to a non-const value). /* value CAN be changed, ptr address CAN'T change */
const int* const ptr3 { &value }; // ptr3 points to an "const int", and it is const itself, so this is a const pointer to a const value. /* neither value nor address can change */

return 0;
}

Changing what a pointer parameter points at : int*& ptr

The Problem

When we pass an address to a function, that address is copied from the argument into the pointer parameter (which is fine, because copying an address is fast). Now consider the following program:

void nullify(int* ptr2)
{
ptr2 = nullptr; // Make the function parameter a null pointer /* BUT willnot change the actual pointer and will only be valid inside this function */
}

int main()
{
int x{ 5 };
int* ptr{ &x }; // ptr points to x

std::cout << "ptr is " << (ptr ? "non-null\n" : "null\n");

nullify(ptr);

std::cout << "ptr is " << (ptr ? "non-null\n" : "null\n");
return 0;
}

// This program prints:
// ptr is non-null
// ptr is non-null

As you can see, changing the address held by the pointer parameter had no impact on the address held by the argument (ptr still points at x). When function nullify() is called, ptr2 receives a copy of the address passed in (in this case, the address held by ptr, which is the address of x). When the function changes what ptr2 points at, this only affects the copy held by ptr2.

So what if we want to allow a function to change what a pointer argument points to? : int*& ptr

The Solution : int*& ptr

Pass by address… by reference? Yup, it's a thing. Just like we can pass a normal variable by reference, we can also pass pointers by reference. Here's the same program as above with ptr2 changed to be a reference to an address:

void nullify(int*& refptr) // refptr is now a reference to a pointer
{
refptr = nullptr; // Make the function parameter a null pointer
}

int main()
{
int x{ 5 };
int* ptr{ &x }; // ptr points to x

std::cout << "ptr is " << (ptr ? "non-null\n" : "null\n");

nullify(ptr);

std::cout << "ptr is " << (ptr ? "non-null\n" : "null\n");
return 0;
}


// This program prints:

// ptr is non-null
// ptr is null

Because refptr is now a reference to a pointer, when ptr is passed as an argument, refptr is bound to ptr. This means any changes to refptr are made to ptr.