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
xxxxxxxxxx
11variable_type *pointer_name;
Example:
xxxxxxxxxx
41int *int_ptr;
2double *double_ptr;
3char *char_ptr;
4string *string_ptr;
Initializing pointer variables to 'point nowhere'
xxxxxxxxxx
11variable_type *pointer_name {nullptr};
Example;
xxxxxxxxxx
41int *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
xxxxxxxxxx
41int num{10};
2cout << "Value of num is: " << num << endl; // 10
3cout << "sizeof of num is: " << sizeof num << endl; // 4
4cout << "Address of num is: " << &num << endl; // 0x61ff1c
xxxxxxxxxx
61int *p;
2cout << "Value of p is: " << p << endl; // 0x61ff60 - garbage
3cout << "Address of p is : " << &p << endl; // 0x61ff18
4cout << "sizeof of p is: " << sizeof p << endl; // 4
5p = nullptr; // Set p to point to nowhere
6cout << "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
xxxxxxxxxx
51int *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.
xxxxxxxxxx
61int score{10};
2double high_temp{100.7};
3
4int *score_ptr{nullptr};
5score_ptr = &score; // OK
6score_ptr = &high_temp; // Compiler Error
Pointers are variables so they can change (Pointers can be null or uninitialized)
xxxxxxxxxx
91double 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 *
.
xxxxxxxxxx
81int score{100};
2int *score_ptr{&score};
3
4cout << *score_ptr << endl; // 100
5
6*score_ptr = 200;
7cout << *score_ptr << endl; // 200
8cout << score << endl; // 200
xxxxxxxxxx
91double high_temp{100.7};
2double low_temp{37.4};
3double *temp_ptr{&high_temp};
4
5cout << *temp_ptr << endl; // 100.7
6
7temp_ptr = &low_temp;
8
9cout << *temp_ptr << endl; 37.4
xxxxxxxxxx
81string name{"Jack"};
2string *string_ptr{&name};
3
4cout << *string_ptr << endl; // Jack
5
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
xxxxxxxxxx
61int *int_ptr{nullptr};
2int_ptr = new int; // Allocate an integer on the heap
3cout << int_ptr << endl; // 0x2747f28
4cout << *int_ptr << endl; // 41188048 - garbage
5*int_ptr = 100;
6cout << *int_ptr << endl; // 100
Using delete
to deallocate storage
xxxxxxxxxx
41int *int_ptr{nullptr};
2int_ptr = new int; // Allocate an integer on the heap
3. . .
4delete int_ptr; // Frees the allocated storage
Using new[]
to allocate storage for an array
xxxxxxxxxx
91int *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 heap
8
9// We can access the array here
Using delete[]
to deallocate storage for an array
xxxxxxxxxx
111int *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 heap
8
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
xxxxxxxxxx
101int scores[]{100, 95, 89};
2
3cout << scores << endl; // 0x61fec8
4cout << *scores << endl; // 100
5
6int *score_ptr{scores};
7
8cout << score_ptr << endl; // 0x61fec8
9
10cout << *score_ptr << endl; // 100
xxxxxxxxxx
71int scores[]{100, 95, 89};
2
3int *score_ptr{scores};
4
5cout << score_ptr[0] << endl; // 100
6cout << score_ptr[1] << endl; // 95
7cout << score_ptr[2] << endl; // 89
Using pointers in expressions
xxxxxxxxxx
71int scores[]{100, 95, 89};
2
3int *score_ptr{scores};
4
5cout << score_ptr << endl; // 0x61ff10
6cout << (score_ptr + 1) << endl; // 0x61ff14
7cout << (score_ptr + 2) << endl; // 0x61ff18
xxxxxxxxxx
71int scores[]{100, 95, 89};
2
3int *score_ptr{scores};
4
5cout << *score_ptr << endl; // 100
6cout << *(score_ptr + 1) << endl; // 95
7cout << *(score_ptr + 2) << endl; // 89
Subscript & Offset notation
xxxxxxxxxx
21int 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
xxxxxxxxxx
11int_ptr++;
--
decrements a pointer to point to the previous array element
xxxxxxxxxx
11int_ptr--;
+
and -
+
increment pointer by n * sizeof(type)
xxxxxxxxxx
11int_ptr += n; // int_ptr = int_ptr + n;
-
increment pointer by n * sizeof(type)
xxxxxxxxxx
11int_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
xxxxxxxxxx
11int 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!)
xxxxxxxxxx
91string s1{"Jack"};
2string s2{"Jack"};
3
4string *p1{&s1};
5string *p2{&s2};
6string *p3{&s3};
7
8cout << (p1 == p2) << endl; // False
9cout << (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.
xxxxxxxxxx
61int high_score{100};
2int low_score{65};
3const int *score_ptr{&high_score};
4
5*score_ptr = 86; // ERROR
6score_ptr = &low_score; // OK
Constant pointers
The data pointed to by the pointers is can be changed.
The pointer itself CANNOT change and point somewhere else.
xxxxxxxxxx
61int high_score{100};
2int low_score{65};
3int *const score_ptr{&high_score};
4
5*score_ptr = 86; // OK
6score_ptr = &low_score; // ERROR
Constant 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.
xxxxxxxxxx
61int high_score{100};
2int low_score{65};
3const int *const score_ptr{&high_score};
4
5*score_ptr = 86; // OK
6score_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
xxxxxxxxxx
61void 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
xxxxxxxxxx
71int main()
2{
3 int value{10};
4 cout << value << endl; // 10
5 double_data(&value);
6 cout << value << endl; // 20
7}
Functions can also return pointers
xxxxxxxxxx
11type* 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
xxxxxxxxxx
151int* 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; // 200
14 return 0;
15}
Returning dynamically allocated memory
xxxxxxxxxx
211int* 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 function
14
15 my_array = create_array(100, 20); // Create the array
16
17 // Use it
18
19 delete []my_array; // Be sure to free the storage
20 return 0;
21}
Never return a pointer to a local variable!
xxxxxxxxxx
141int* 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
xxxxxxxxxx
31int *int_ptr; // Pointing anywhere
2. . .
3*int_ptr = 100; // Hopefully a crash
Dangling 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