Зміст курсу
C++ Smart Pointers
C++ Smart Pointers
Understanding Pointers and Heap Memory
What is a pointer
Pointers are an essential concept in the world of C++. Instead of storing actual data, pointers store memory addresses, allowing developers to efficiently access and manipulate various parts of the memory.
Note
You can think of pointers as GPS coordinates. They point us to specific locations in the memory, and allow us to access data stored in those locations.
When you declare a pointer, you create a variable that holds the memory address of another variable. For example, you can create a pointer to an integer like this:
ptr
holds the memory address of the integermyValue
.- The
&
sign is used to get the memory address of the variable it’s used with. &myValue
retrieves the memory address of themyValue
variable, which is then stored in theptr
pointer.
Null pointers
In C++, null
or nullptr
are special keywords used to represent a null pointer. A null pointer *doesn't point to any valid memory address. It's essentially a pointer with no target.
Note
We can also initialize the integer pointer to
null
(ornullptr
). We do this when we don't want to associate the pointer with a specific value right away.
Reassigning pointers
Pointers can be reassigned:
Pointer arithmetic
Pointer arithmetic is a fascinating aspect of pointers. It allows you to traverse memory by incrementing or decrementing the address held by a pointer.
For example, consider the following code where we create an array of integers and then define a pointer to hold the address of the array. Since an array contains multiple elements, a pointer, by default, stores the address of the first element, which in this case is the integer 1.
To access the second element, we can simply increment the pointer by 1. Then, we finally dereference it using the (*
) operator. Derefencing returns the value present at the memory address held by the pointer (the integer 2 in our case). Follow the comments in the above code to know what’s happening!
Dynamic memory allocation
There are two kinds of memory available for use in a C++ program: stack and heap. Stack memory is limited in size. It’s well-suited for small, short-lived variables that aren’t needed beyond their scope. When a variable on the stack goes out of scope, it’s automatically destroyed.
However, when you need memory that persists longer than its scope, or requires more space, you turn to the heap. The heap is a much larger pool of memory where your program can request memory at runtime. This is typically done using the new
keyword:
Here, we've allocated memory for an integer on the heap and stored its address in a pointer. The tricky thing about heap memory is that it’s not automatically released. It’s the developer’s responsibility to deallocate it when they are done. For every new
, there must be a corresponding delete
.
Memory leaks and dangling pointers
When you introduce pointers into your C++ code, you enter the realm of manual memory management. Manual memory management requires you to release the memory when it's no longer needed. It can lead to common pitfalls: memory leaks and dangling pointers.
Memory leaks
Memory leaks typically occur when you forget to deallocate memory. Such memory remains reserved, causing your program to lose memory over time.
Example
You create dynamic objects in a loop, but never delete them, they will lead to a memory leak.
Dangling pointers
Dangling pointers occur when a pointer continues to point to a memory that’s been deallocated. It's like having a treasure map that leads you to a place (memory address) where the treasure (data) has already been taken (destroyed).
main
#include <iostream> int main() { int* ptr = new int; // dynamically allocate an integer *ptr = 42; // assign a value to the allocated memory std::cout << *ptr << std::endl; // This will output 42 delete ptr; // deallocate the memory std::cout << *ptr << std::endl; // ptr is a dangling pointer! }
In the above code, we try to access a pointer after calling delete
on it, which deallocates the memory it was pointing to. After deletion, the pointer becomes a dangling pointer, as it still holds the address to a memory that no longer exists.
Accessing a dangling pointer can lead to unpredictable behavior or even crashes, as the memory it points to is no longer valid. To avoid dangling pointers, it’s important to set a pointer to “null” after calling delete on it. Then, before accessing the value of that pointer, it’s imperative to check whether it’s null or not. Consider the following code:
main
#include <iostream> int main() { // Dynamically allocate an integer int* ptr = new int; // Assign a value to the allocated memory *ptr = 42; // Output the value, which will be 42 std::cout << *ptr << std::endl; // Free the allocated memory to avoid memory leaks delete ptr; // Set the pointer to null ptr = nullptr; // Check whether the pointer is not null if (ptr != nullptr) // Access and output the value, which won't execute std::cout << *ptr << std::endl; }
Дякуємо за ваш відгук!