Welcome! Today, we're diving into a crucial topic in C++: "Working with Dynamic Memory and Creating Objects in the Heap." Understanding dynamic memory allocation allows for more flexible memory management, especially when you need to allocate space at runtime.
In some programs, we don't know how much memory we need until runtime. For instance, if you're creating a game that loads levels, you might not know how many objects a level contains until it loads. Dynamic memory helps by letting you allocate memory exactly when you need it.
Ready? Let's get started!
Think of your computer's memory as a bookshelf (stack) and a storage room (heap). The stack is small but quick to access, like a bookshelf where you can easily pick up your favorite book. The heap is larger but slower to access, like a storage room full of boxes.
- Stack Memory: Automatically managed and used for local variables. It's faster but limited in size.
- Heap Memory: Manually managed, useful for dynamic memory allocation. It's larger and more flexible but needs careful handling to avoid memory leaks.
To allocate memory in the heap, use the new
keyword. This creates an object or an array of objects and returns a pointer to the memory location. One crucial thing to know is that you must deallocate the used dynamic memory. This memory will be blocked until the PC restarts if you don't do it. This problem is known as a memory leak
.
Let's start with a simple example:
C++1#include <iostream> 2 3int main() { 4 // Allocating an integer in the heap 5 int* intPtr = new int(10); 6 7 // Accessing the integer 8 std::cout << "Value: " << *intPtr << std::endl; // Output: Value: 10 9 10 // Free the allocated memory to prevent memory leaks 11 delete intPtr; 12 return 0; 13}
In this example:
new int(10)
: Allocates memory for an integer in the heap and initializes it with 10.int* intPtr
: Stores the address of the allocated memory.*intPtr
: Dereferences the pointer to access the value.delete intPtr
: Deallocates the memory to prevent memory leaks.
Dynamic arrays are another common use of heap memory. Allocating an array dynamically allows its size to be determined at runtime.
Example:
C++1#include <iostream> 2 3int main() { 4 // Allocating an array of integers in the heap 5 int* arrayPtr = new int[5]; 6 7 // Initializing the array 8 for (int i = 0; i < 5; ++i) { 9 arrayPtr[i] = i * 10; 10 } 11 12 // Accessing and displaying the array elements 13 for (int i = 0; i < 5; ++i) { 14 std::cout << "Value at index " << i << ": " << arrayPtr[i] << std::endl; 15 } 16 17 // Free the allocated memory to prevent memory leaks 18 delete[] arrayPtr; 19 return 0; 20}
In this example:
new int[5]
: Allocates memory for an array of 5 integers in the heap.arrayPtr[i]
: Accesses and manipulates elements using the pointer.delete[] arrayPtr
: Deallocates the array memory to prevent memory leaks.
A memory leak is mostly dangerous when initializing dynamic arrays as they could take a lot of RAM space. Every call to new
must be paired with a corresponding delete
, and every new[]
must be paired with a delete[]
.
Next, let's create an object dynamically and understand how to access and use it. Consider this class:
C++1#include <iostream> 2 3class Person { 4private: 5 std::string name; 6 int age; 7 8public: 9 Person(std::string n, int a) : name(n), age(a) {} 10 11 void display() { 12 std::cout << "Name: " << name << ", Age: " << age << std::endl; 13 } 14};
We can create an object of this class in the heap using the new
keyword:
C++1int main() { 2 // Creating an object in the heap 3 Person* personPtr = new Person("John", 30); 4 5 // Free the allocated memory to prevent memory leaks 6 delete personPtr; 7 return 0; 8}
When you allocate memory dynamically, you use pointers to access and use objects. A pointer stores the object's memory address.
Usually we access the object's methods and fields using the .
. You might think that when you have a pointer to an object, you can access its fields like this:
C++1int main() { 2 *personPtr.display(); 3}
Here, we try to dereference the pointer and then use .
to access the display
method. However, it won't work, as the .
operator has a higher execution priority than the *
operator. So, the correct way would be this:
C++1int main() { 2 // Creating an object in the heap 3 Person* personPtr = new Person("John", 30); 4 5 (*personPtr).display(); // Name: John, Age: 30 6 7 // Free the allocated memory to prevent memory leaks 8 delete personPtr; 9 return 0; 10}
Using *
and .
can be a bit confusing. There is a shorter and easier way to do the same thing: using ->
operator. The ->
combines dereferencing the pointer and accessing the class member in one operator.
Instead of
C++1(*personPtr).display();
You can do this:
C++1personPtr->display();
And this will work exactly the same!
Here is the complete code:
C++1int main() { 2 // Creating an object in the heap 3 Person* personPtr = new Person("John", 30); 4 5 personPtr->display(); // Name: John, Age: 30 6 7 // Free the allocated memory to prevent memory leaks 8 delete personPtr; 9 return 0; 10}
Calling a method on already deleted memory can cause undefined behavior. For example:
C++1#include <iostream> 2 3int main() { 4 // Creating an object in the heap 5 Person* personPtr = new Person("John", 30); 6 7 // Free the allocated memory 8 delete personPtr; 9 10 // Attempting to call a method on a deleted object 11 // This can cause a crash or undefined behavior 12 personPtr->display(); // Undefined behavior 13 14 return 0; 15}
After delete personPtr;
, the memory allocated for personPtr
is deallocated. Attempting to access it afterward can lead to crashes or unexpected behavior. Hence, it's important to set the pointer to nullptr
after deletion to avoid dangling pointers:
C++1delete personPtr; 2personPtr = nullptr; // Prevents accidental access
Let's recap:
- Dynamic Memory Allocation: We learned how to use
new
anddelete
for heap memory. - Dynamic Arrays: We explored creating and managing dynamic arrays using
new[]
anddelete[]
. - Accessing Objects in the Heap: We used pointers to manipulate dynamically allocated objects.
- Memory Management: The importance of freeing allocated memory to prevent memory leaks.
- Avoiding Dangling Pointers: We learned the importance of setting pointers to
nullptr
after deallocation.
Great job! Understanding and managing dynamic memory is vital for proficient C++ programming.
Now it's time to put what you've learned into practice. You'll get hands-on experience with dynamic memory allocation, working with pointers, and managing memory effectively. Let's move on to the practice exercises!