Home | Projects | Notes > C++ Programming > Pointers
What is a pointer?
Declaring pointers
Storing addresses in pointers
Dereferencing pointers
Dynamic memory allocation
Pointer arithmetic
Pointers and arrays
Pass-by-reference with pointers
const and pointers
Using pointers to functions
Potential pointer pitfalls
A variable whose value is an address
What can be at that address?
Another variable
A function
Pointers point to variables or functions?
If x is an integer variable and its value is 10, then I can declare a pointer that points to it.
To use the data that the pointer is pointing to you must know its type
Can't I just use the variable or function itself?
Inside functions, pointers can be used to access data that are defined outside the function. Those variables may not be in scope so you can't access them by their name.
Pointers can be used to operate on arrays very efficiently.
We can allocate memory dynamically on the heap or free store.
This memory doesn't even have a variable name.
The only way to get to it is via a pointer.
With OO, pointers are how polymorphism works!
Can access specific addresses in memory.
Useful in embedded and systems applications
Declaration
xxxxxxxxxx11variable_type *pointer_name;Example:
xxxxxxxxxx41int *int_ptr;2double *double_ptr;3char *char_ptr;4string *string_ptr;Initializing pointer variables to 'point nowhere'
xxxxxxxxxx11variable_type *pointer_name {nullptr};Example;
xxxxxxxxxx41int *int_ptr{};2double *double_ptr{nullptr};3char *char_ptr{nullptr};4string *string_ptr{nullptr};Always initialize pointers
Uninitialized pointers contain garbage data and can 'point anywhere'.
Initializing to zero or nullptr (C++11) represents address zero
Implies that the pointer is 'pointing nowhere'
If you don't initialize a pointer to point to a variable or function then you should initialize it to nullptr to 'make it null'.
& - The address operator
Variables are stored in unique addresses
Unary operator
Evaluates to the address of its operand
Operand cannot be a constant or expression that evaluates to temp values
Example
xxxxxxxxxx41int num{10};2cout << "Value of num is: " << num << endl; // 103cout << "sizeof of num is: " << sizeof num << endl; // 44cout << "Address of num is: " << &num << endl; // 0x61ff1cxxxxxxxxxx61int *p;2cout << "Value of p is: " << p << endl; // 0x61ff60 - garbage3cout << "Address of p is : " << &p << endl; // 0x61ff184cout << "sizeof of p is: " << sizeof p << endl; // 45p = nullptr; // Set p to point to nowhere6cout << "Value of p is: " << p << endl; // 0
sizeof a Pointer VariableDon't confuse the size of a pointer and the size of what it points to.
All pointers in a program have the same size.
They may be pointing to very large or very small types.
Example
xxxxxxxxxx51int *p1{nullptr};2double *p2{nullptr};3unsigned long long *p3{nullptr};4vector<string> *p4{nullptr};5string *p5{nullptr};
The compiler will make sure that the address stored in a pointer variable is of the correct type.
xxxxxxxxxx61int score{10};2double high_temp{100.7};3
4int *score_ptr{nullptr};5score_ptr = &score; // OK6score_ptr = &high_temp; // Compiler ErrorPointers are variables so they can change (Pointers can be null or uninitialized)
xxxxxxxxxx91double high_temp{100.7};2double low_temp{37.2};3
4double *temp_ptr;5
6temp_ptr = &high_temp; // Points to high_temp;7temp_ptr = &low_temp; // Now points to low_temp;8
9temp_ptr = nullptr;
Dereferencing a pointer means accessing the data the pointer is pointing to.
If score_ptr is a pointer and has a valid address, then you can access the data at the address contained in the score_ptr using the dereferencing operator *.
xxxxxxxxxx81int score{100};2int *score_ptr{&score};3
4cout << *score_ptr << endl; // 1005
6*score_ptr = 200;7cout << *score_ptr << endl; // 2008cout << score << endl; // 200xxxxxxxxxx91double high_temp{100.7};2double low_temp{37.4};3double *temp_ptr{&high_temp};4
5cout << *temp_ptr << endl; // 100.76
7temp_ptr = &low_temp;8 9cout << *temp_ptr << endl; 37.4xxxxxxxxxx81string name{"Jack"};2string *string_ptr{&name};3
4cout << *string_ptr << endl; // Jack5
6name = "Yena";7
8cout << *string_ptr << endl; // Yena
new & delete)Allocating storage from the heap at run-time
We often don't know how much storage we need until we need it.
We can allocate storage for a variable at run-time
Recall C++ arrays
We had to explicitly provide the size and it was fixed.
But, vectors grow and shrink dynamically.
We can use pointers to access newly allocated heap storage.
Using new to allocate storage
xxxxxxxxxx61int *int_ptr{nullptr};2int_ptr = new int; // Allocate an integer on the heap3cout << int_ptr << endl; // 0x2747f284cout << *int_ptr << endl; // 41188048 - garbage5*int_ptr = 100;6cout << *int_ptr << endl; // 100Using delete to deallocate storage
xxxxxxxxxx41int *int_ptr{nullptr};2int_ptr = new int; // Allocate an integer on the heap3. . .4delete int_ptr; // Frees the allocated storageUsing new[] to allocate storage for an array
xxxxxxxxxx91int *array_ptr{nullptr};2int size{};3
4cout << "How big do you want the array?";5cin >> size;6
7array_ptr = new int[size]; // Allocate array on the heap8
9// We can access the array hereUsing delete[] to deallocate storage for an array
xxxxxxxxxx111int *array_ptr{nullptr};2int size{};3
4cout << "How big do you want the array?";5cin >> size;6
7array_ptr = new int[size]; // Allocate array on the heap8
9. . .10 11delete []array_ptr; // Free Allocated storage
The value of an array name is the address of the first element in the array.
The value of a pointer variable is an address.
If the pointer points to the same data type as the array element then the pointer and array name can be used in interchangeably (almost).
Example
xxxxxxxxxx101int scores[]{100, 95, 89};2
3cout << scores << endl; // 0x61fec84cout << *scores << endl; // 1005
6int *score_ptr{scores};7
8cout << score_ptr << endl; // 0x61fec89
10cout << *score_ptr << endl; // 100xxxxxxxxxx71int scores[]{100, 95, 89};2
3int *score_ptr{scores};4
5cout << score_ptr[0] << endl; // 1006cout << score_ptr[1] << endl; // 957cout << score_ptr[2] << endl; // 89Using pointers in expressions
xxxxxxxxxx71int scores[]{100, 95, 89};2
3int *score_ptr{scores};4
5cout << score_ptr << endl; // 0x61ff106cout << (score_ptr + 1) << endl; // 0x61ff147cout << (score_ptr + 2) << endl; // 0x61ff18xxxxxxxxxx71int scores[]{100, 95, 89};2
3int *score_ptr{scores};4
5cout << *score_ptr << endl; // 1006cout << *(score_ptr + 1) << endl; // 957cout << *(score_ptr + 2) << endl; // 89
Subscript & Offset notation
xxxxxxxxxx21int array_name[]{1, 2, 3, 4, 5};2int *pointer_name{array_name};| Subscript Notation | Offset Notation |
|---|---|
| array_name[index] | *(array_name + index) |
| pointer_name[index] | *(pointer_name + index) |
Pointers can be used in
Assignment expressions
Arithmetic expressions
Comparison expressions
C++ allows pointer arithmetic
Pointer arithmetic only makes sense with raw arrays
++ and --
++ increments a pointer to point to the next array element
xxxxxxxxxx11int_ptr++;-- decrements a pointer to point to the previous array element
xxxxxxxxxx11int_ptr--;+ and -
+ increment pointer by n * sizeof(type)
xxxxxxxxxx11int_ptr += n; // int_ptr = int_ptr + n;- increment pointer by n * sizeof(type)
xxxxxxxxxx11int_ptr -= n; // int_ptr = int_ptr - n;Subtracting two pointers
Determine the number of elements between the pointers
Both pointers must point to the same data type
xxxxxxxxxx11int n = int_ptr2 - int_ptr1;Comparing two pointers (== and !=)
Determine if two pointers point to the same location (Does NOT compare the data where they point!)
xxxxxxxxxx91string s1{"Jack"};2string s2{"Jack"};3
4string *p1{&s1};5string *p2{&s2};6string *p3{&s3};7
8cout << (p1 == p2) << endl; // False9cout << (p1 == p3) << endl; // True
const and PointersThere are several ways to qualify pointers using const.
Pointers to constants
The data pointed to by the pointers is constant and CANNOT be changed.
The pointer itself can change and point somewhere else.
xxxxxxxxxx61int high_score{100};2int low_score{65};3const int *score_ptr{&high_score};4
5*score_ptr = 86; // ERROR6score_ptr = &low_score; // OKConstant pointers
The data pointed to by the pointers is can be changed.
The pointer itself CANNOT change and point somewhere else.
xxxxxxxxxx61int high_score{100};2int low_score{65};3int *const score_ptr{&high_score};4
5*score_ptr = 86; // OK6score_ptr = &low_score; // ERRORConstant pointers to constants
The data pointed to by the pointers is constant and CANNOT be changed.
The pointer itself CANNOT change and point somewhere else.
xxxxxxxxxx61int high_score{100};2int low_score{65};3const int *const score_ptr{&high_score};4
5*score_ptr = 86; // OK6score_ptr = &low_score; // ERROR
Pass-by-reference with pointer parameters
We can use pointers and the dereference operator to achieve pass-by-reference
The function parameter is a pointer
The actual parameter can be a pointer or address of a variable
Pass-by-reference with pointers
Defining the function
xxxxxxxxxx61void double_data(int *int_ptr);2
3void double_data(int *int_ptr);4{5 *int_ptr *= 2; // *int_ptr = *int_ptr * 2;6}Calling the function
xxxxxxxxxx71int main()2{3 int value{10};4 cout << value << endl; // 105 double_data(&value);6 cout << value << endl; // 207}
Functions can also return pointers
xxxxxxxxxx11type* function();Should return pointers to
Memory dynamically allocated in the function
To data that was passed in (i.e., parameter)
Never return a pointer to a local function variable!
Returning a paramter
xxxxxxxxxx151int* largest_int(int *int_ptr1, int *int_ptr2)2{3 return (*int_ptr1 > *int_ptr2) ? int_ptr1 : int_ptr2;4}5
6int main(void)7{8 int a{100};9 int b{200};10 11 int *largest_ptr{nullptr};12 largest_ptr = largest_int(&a, &b);13 cout << *largest_ptr << endl; // 20014 return 0;15}Returning dynamically allocated memory
xxxxxxxxxx211int* create_array(size_t size, int init_value = 0)2{3 int *new_storage{nullptr};4 5 new_storage = new int[size];6 for (size_t i{0}; i < size; ++i)7 *(new_storage + i) = init_value;8 return new_storage;9}10
11int main(void)12{13 int *my_array; // Will be allocated by the function14 15 my_array = create_array(100, 20); // Create the array16 17 // Use it18 19 delete []my_array; // Be sure to free the storage20 return 0;21}Never return a pointer to a local variable!
xxxxxxxxxx141int* dont_do_this()2{3 int size{};4 . . .5 return &size;6}7
8int* or_this()9{10 int size{};11 int *int_ptr{&size};12 . . .13 return int_ptr;14}Local variables will no longer exist outside of the function body in which they are declared. Thus, the returned pointer will point to a location that doesn't even exist.
Dangling pointer!
Uninitialized pointers
xxxxxxxxxx31int *int_ptr; // Pointing anywhere2. . .3*int_ptr = 100; // Hopefully a crashDangling pointers
Pointer that is pointing to released memory
e.g., 2 pointers point to the same data, 1 pointer releases the data with delete, the other pointer accesses the released data
Pointer that points to memory that is invalid
e.g., When a pointer to a local variable in a function is returned
Not checking if new failed to allocate memory
If new fails an exception is thrown.
We can use exception handling to catch exceptions.
Dereferencing a null pointer will cause your program to crash
Memory leak
Forgetting to release allocated memory with delete
If you lose your pointer to the storage allocated on the heap you don't have way to get to that storage again!
The memory is orphaned or leaked
One of the most common pointer problems!
Pass-by-value
When the function does NOT modify the actual parameter
When the parameter is small and efficient to copy like simple types (int, char, double, etc.)
Pass-by-reference using a pointer
When the function does modify the actual parameter
When the parameter is expensive to copy
It's OK for the pointer to contain a nullptr value
Pass-by-reference using a pointer to const
When the function does NOT modify the actual parameter
When the parameter is expensive to copy
It's OK for the pointer to contain a nullptr value
Pass-by-reference using a const pointer to const
When the function does NOT modify the actual parameter
When the parameter is expensive to copy
It's OK for the pointer to contain a nullptr value
When you don't want to modify the pointer itself
Pass-by-reference using a reference
When the function DOES modify the actual parameter
When the parameter is expensive to copy
The parameter will never be nullptr
Pass-by-reference using a const reference
When the function does NOT modify the actual parameter
When the parameter is expensive to copy
The parameter will never be nullptr