Introduction
In this lab, you will learn how to handle dynamic memory allocation in C++. You will start by allocating memory using the new operator, then release the memory with the delete operator. You will also explore creating dynamic arrays, using smart pointers like shared_ptr and unique_ptr, and checking for memory leaks. These skills are essential for effective memory management in C++ programming.
The lab covers the following key steps: allocating memory using the new operator, releasing memory with the delete operator, creating dynamic arrays with new[], deleting arrays using delete[], implementing the shared_ptr smart pointer, using unique_ptr for exclusive ownership, checking for memory leaks, and handling memory allocation failures.
Allocate Memory Using new Operator
In this step, you'll learn how to use the new operator in C++ to dynamically allocate memory at runtime. Dynamic memory allocation allows you to create variables and arrays whose size is determined during program execution, providing more flexibility compared to static memory allocation.
Open the WebIDE and create a new file called dynamic_memory.cpp in the ~/project directory:
touch ~/project/dynamic_memory.cpp
Add the following code to dynamic_memory.cpp:
#include <iostream>
int main() {
// Dynamically allocate an integer using new
int* dynamicInteger = new int;
// Assign a value to the dynamically allocated integer
*dynamicInteger = 42;
// Print the value of the dynamically allocated integer
std::cout << "Dynamically allocated integer value: " << *dynamicInteger << std::endl;
// Print the memory address of the dynamically allocated integer
std::cout << "Memory address: " << dynamicInteger << std::endl;
return 0;
}
Let's break down the key concepts:
int* dynamicInteger = new int;:newoperator allocates memory for an integer- Returns a pointer to the allocated memory
- Creates the variable on the heap, not the stack
*dynamicInteger = 42;:- Uses the dereference operator
*to assign a value - Stores the value 42 in the dynamically allocated memory
- Uses the dereference operator
Compile and run the program:
g++ dynamic_memory.cpp -o dynamic_memory
./dynamic_memory
Example output:
Dynamically allocated integer value: 42
Memory address: 0x55f4e8a042a0
Key points about new operator:
- Allocates memory dynamically during runtime
- Returns a pointer to the allocated memory
- Memory is allocated on the heap
- Size can be determined at runtime
- Allows for more flexible memory management
Note: In the next steps, you'll learn how to properly release this dynamically allocated memory to prevent memory leaks.
Release Memory with delete Operator
In this step, you'll learn how to use the delete operator to properly release dynamically allocated memory in C++. Failing to release memory can lead to memory leaks, which can cause performance issues in your program.
Open the WebIDE and create a new file called memory_release.cpp in the ~/project directory:
touch ~/project/memory_release.cpp
Add the following code to memory_release.cpp:
#include <iostream>
int main() {
// Dynamically allocate an integer
int* dynamicInteger = new int;
// Assign a value to the dynamically allocated integer
*dynamicInteger = 42;
// Print the value before releasing memory
std::cout << "Value before release: " << *dynamicInteger << std::endl;
// Release the dynamically allocated memory using delete
delete dynamicInteger;
// Set the pointer to nullptr after deletion
dynamicInteger = nullptr;
// Demonstrate safe pointer usage
if (dynamicInteger == nullptr) {
std::cout << "Memory has been successfully released" << std::endl;
}
return 0;
}
Key points about the delete operator:
delete dynamicInteger;:- Frees the memory previously allocated with
new - Prevents memory leaks
- Releases the memory back to the system
- Frees the memory previously allocated with
dynamicInteger = nullptr;:- Sets the pointer to
nullptrafter deletion - Helps prevent accidental use of freed memory
- Provides an additional safety check
- Sets the pointer to
Compile and run the program:
g++ memory_release.cpp -o memory_release
./memory_release
Example output:
Value before release: 42
Memory has been successfully released
Important memory management rules:
- Always use
deletefor memory allocated withnew - Set pointers to
nullptrafter deletion - Never use a pointer after it has been deleted
- Each
newshould have a correspondingdelete
Create Dynamic Arrays with new[]
In this step, you'll learn how to create dynamic arrays using the new[] operator in C++. Dynamic arrays allow you to allocate memory for multiple elements at runtime, providing more flexibility than static arrays.
Open the WebIDE and create a new file called dynamic_array.cpp in the ~/project directory:
touch ~/project/dynamic_array.cpp
Add the following code to dynamic_array.cpp:
#include <iostream>
int main() {
// Declare the size of the dynamic array
int arraySize = 5;
// Create a dynamic integer array using new[]
int* dynamicArray = new int[arraySize];
// Initialize array elements
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// Print array elements
std::cout << "Dynamic Array Contents:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
return 0;
}
Key points about dynamic arrays:
int* dynamicArray = new int[arraySize];:- Allocates memory for an array of integers
arraySizedetermines the number of elements- Memory is allocated on the heap
- Size can be determined at runtime
Array initialization and access:
- Use standard array indexing
dynamicArray[i] - Can be manipulated like a regular array
- Allows dynamic sizing based on runtime conditions
- Use standard array indexing
Compile and run the program:
g++ dynamic_array.cpp -o dynamic_array
./dynamic_array
Example output:
Dynamic Array Contents:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
Important considerations:
- Dynamic arrays provide flexibility in memory allocation
- Size can be determined at runtime
- Remember to use
new[]for allocation anddelete[]for deallocation - Always match the allocation and deallocation methods
Delete Arrays Using delete[]
In this step, you'll learn how to properly release memory allocated for dynamic arrays using the delete[] operator. Proper memory management is crucial to prevent memory leaks and ensure efficient resource utilization.
Open the WebIDE and create a new file called delete_array.cpp in the ~/project directory:
touch ~/project/delete_array.cpp
Add the following code to delete_array.cpp:
#include <iostream>
int main() {
// Declare the size of the dynamic array
int arraySize = 5;
// Create a dynamic integer array using new[]
int* dynamicArray = new int[arraySize];
// Initialize array elements
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// Print array elements before deletion
std::cout << "Array Contents Before Deletion:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
// Release the dynamically allocated array using delete[]
delete[] dynamicArray;
// Set the pointer to nullptr after deletion
dynamicArray = nullptr;
// Demonstrate safe pointer usage
if (dynamicArray == nullptr) {
std::cout << "Dynamic array has been successfully deleted" << std::endl;
}
return 0;
}
Key points about deleting dynamic arrays:
delete[] dynamicArray;:- Properly releases memory for entire array
- Must use
delete[]for arrays allocated withnew[] - Prevents memory leaks
- Calls destructor for each array element
dynamicArray = nullptr;:- Sets pointer to
nullptrafter deletion - Prevents accidental access to freed memory
- Provides an additional safety check
- Sets pointer to
Compile and run the program:
g++ delete_array.cpp -o delete_array
./delete_array
Example output:
Array Contents Before Deletion:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
Dynamic array has been successfully deleted
Important memory management rules:
- Always use
delete[]for arrays allocated withnew[] - Never mix
deleteanddelete[] - Set pointers to
nullptrafter deletion - Each
new[]should have a correspondingdelete[]
Implement Smart Pointer shared_ptr
In this step, you'll learn about shared_ptr, a smart pointer from the C++ Standard Library that provides automatic memory management and reference counting. Smart pointers help prevent memory leaks and simplify memory management.
Open the WebIDE and create a new file called shared_pointer.cpp in the ~/project directory:
touch ~/project/shared_pointer.cpp
Add the following code to shared_pointer.cpp:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int value) : data(value) {
std::cout << "Constructor called. Value: " << data << std::endl;
}
~MyClass() {
std::cout << "Destructor called. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
int main() {
// Create a shared_ptr to MyClass
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);
// Create another shared_ptr pointing to the same object
std::shared_ptr<MyClass> ptr2 = ptr1;
// Print reference count
std::cout << "Reference Count: " << ptr1.use_count() << std::endl;
// Access object through shared_ptr
std::cout << "Data from ptr1: " << ptr1->getData() << std::endl;
std::cout << "Data from ptr2: " << ptr2->getData() << std::endl;
// Pointers will automatically free memory when they go out of scope
return 0;
}
Key points about shared_ptr:
std::make_shared<MyClass>(42):- Creates a shared pointer with dynamic memory allocation
- Initializes object with constructor argument
- More efficient than separate
newandshared_ptrcreation
ptr1.use_count():- Returns number of shared_ptr instances pointing to the same object
- Helps track object's reference count
Automatic memory management:
- Memory is automatically freed when no shared_ptr references the object
- Prevents manual memory management errors
Compile and run the program:
g++ -std=c++11 shared_pointer.cpp -o shared_pointer
./shared_pointer
Example output:
Constructor called. Value: 42
Reference Count: 2
Data from ptr1: 42
Data from ptr2: 42
Destructor called. Value: 42
Important shared_ptr characteristics:
- Automatic memory management
- Reference counting
- Prevents memory leaks
- Thread-safe reference counting
Use unique_ptr for Exclusive Ownership
In this step, you'll learn about unique_ptr, a smart pointer that provides exclusive ownership of dynamically allocated resources. Unlike shared_ptr, unique_ptr ensures that only one pointer can own the resource at a time.
Open the WebIDE and create a new file called unique_pointer.cpp in the ~/project directory:
touch ~/project/unique_pointer.cpp
Add the following code to unique_pointer.cpp:
#include <iostream>
#include <memory>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created. Value: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
void processResource(std::unique_ptr<Resource> resource) {
std::cout << "Processing resource with value: " << resource->getData() << std::endl;
// Resource will be automatically deleted when function ends
}
int main() {
// Create a unique_ptr
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>(42);
// Print resource data
std::cout << "Resource value: " << ptr1->getData() << std::endl;
// Transfer ownership using std::move
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// ptr1 is now nullptr
if (ptr1 == nullptr) {
std::cout << "ptr1 is now null after moving ownership" << std::endl;
}
// Pass unique_ptr to a function (ownership is transferred)
processResource(std::move(ptr2));
return 0;
}
Key points about unique_ptr:
std::make_unique<Resource>(42):- Creates a unique pointer with dynamic memory allocation
- Ensures exclusive ownership of the resource
std::move(ptr1):- Transfers ownership of the resource
- Original pointer becomes
nullptr - Prevents multiple pointers from owning the same resource
Automatic memory management:
- Resource is automatically deleted when unique_ptr goes out of scope
- No manual memory management required
Compile and run the program:
g++ -std=c++14 unique_pointer.cpp -o unique_pointer
./unique_pointer
Example output:
Resource created. Value: 42
Resource value: 42
ptr1 is now null after moving ownership
Processing resource with value: 42
Resource destroyed. Value: 42
Important unique_ptr characteristics:
- Exclusive ownership
- Automatic memory management
- Cannot be copied, only moved
- Prevents resource leaks
Check for Memory Leaks
In this step, you'll learn how to detect and prevent memory leaks in C++ using Valgrind, a powerful memory debugging tool. Memory leaks occur when dynamically allocated memory is not properly freed, causing your program to consume increasing amounts of memory.
First, install Valgrind in the terminal:
sudo apt update
sudo apt install -y valgrind
Create a memory leak example in the WebIDE by creating memory_leak.cpp in the ~/project directory:
touch ~/project/memory_leak.cpp
Add the following code to memory_leak.cpp:
#include <iostream>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed: " << data << std::endl;
}
private:
int data;
};
void createMemoryLeak() {
// This creates a memory leak because the resource is not deleted
Resource* leak = new Resource(42);
// Missing: delete leak;
}
int main() {
// Simulate multiple memory leaks
for (int i = 0; i < 3; ++i) {
createMemoryLeak();
}
return 0;
}
Compile the program with debugging symbols:
g++ -g memory_leak.cpp -o memory_leak
Run Valgrind to detect memory leaks:
valgrind --leak-check=full ./memory_leak
Example Valgrind output:
==1988== Memcheck, a memory error detector
==1988== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1988== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==1988== Command: ./memory_leak
==1988==
Resource created: 42
Resource created: 42
Resource created: 42
==1988==
==1988== HEAP SUMMARY:
==1988== in use at exit: 12 bytes in 3 blocks
==1988== total heap usage: 5 allocs, 2 frees, 73,740 bytes allocated
==1988==
==1988== 12 bytes in 3 blocks are definitely lost in loss record 1 of 1
==1988== at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1988== by 0x109241: createMemoryLeak() (memory_leak.cpp:19)
==1988== by 0x109299: main (memory_leak.cpp:26)
==1988==
==1988== LEAK SUMMARY:
==1988== definitely lost: 12 bytes in 3 blocks
==1988== indirectly lost: 0 bytes in 0 blocks
==1988== possibly lost: 0 bytes in 0 blocks
==1988== still reachable: 0 bytes in 0 blocks
==1988== suppressed: 0 bytes in 0 blocks
==1988==
==1988== For lists of detected and suppressed errors, rerun with: -s
==1988== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
As you can see, Valgrind detected a memory leak of 12 bytes in 3 blocks. The Resource object created in createMemoryLeak() was not deleted, causing the memory leak.
To fix the memory leak, modify the code to properly delete resources:
void createMemoryLeak() {
// Properly delete the dynamically allocated resource
Resource* leak = new Resource(42);
delete leak; // Add this line to prevent memory leak
}
Compile and run the program again with Valgrind to verify that the memory leak is fixed:
g++ -g memory_leak.cpp -o memory_leak
valgrind --leak-check=full ./memory_leak
==2347== Memcheck, a memory error detector
==2347== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2347== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2347== Command: ./memory_leak
==2347==
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
==2347==
==2347== HEAP SUMMARY:
==2347== in use at exit: 0 bytes in 0 blocks
==2347== total heap usage: 5 allocs, 5 frees, 73,740 bytes allocated
==2347==
==2347== All heap blocks were freed -- no leaks are possible
==2347==
==2347== For lists of detected and suppressed errors, rerun with: -s
==2347== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Key points about memory leaks:
- Always
deletedynamically allocated memory - Use smart pointers like
unique_ptrandshared_ptr - Use Valgrind to detect memory leaks
- Compile with
-gflag for better debugging information
Handle Memory Allocation Failures
In this step, you'll learn how to handle memory allocation failures in C++. When dynamic memory allocation fails, the new operator throws a std::bad_alloc exception, which you can catch and handle gracefully.
Open the WebIDE and create a new file called memory_allocation.cpp in the ~/project directory:
touch ~/project/memory_allocation.cpp
Add the following code to memory_allocation.cpp:
#include <iostream>
#include <new>
#include <limits>
void demonstrateMemoryAllocation() {
try {
// Attempt to allocate a large amount of memory
const size_t largeSize = 1000000000000; // 1 trillion integers
int* largeArray = new int[largeSize];
// This line will not be reached if allocation fails
std::cout << "Memory allocation successful" << std::endl;
// Clean up allocated memory
delete[] largeArray;
}
catch (const std::bad_alloc& e) {
// Handle memory allocation failure
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}
}
void safeMemoryAllocation() {
// Use std::nothrow to prevent exception throwing
int* safeArray = new(std::nothrow) int[1000000];
if (safeArray == nullptr) {
std::cerr << "Memory allocation failed silently" << std::endl;
return;
}
// Use the allocated memory
std::cout << "Safe memory allocation successful" << std::endl;
// Clean up allocated memory
delete[] safeArray;
}
int main() {
std::cout << "Demonstrating memory allocation failure handling:" << std::endl;
// Method 1: Using exception handling
demonstrateMemoryAllocation();
// Method 2: Using std::nothrow
safeMemoryAllocation();
return 0;
}
Key points about memory allocation failure handling:
Exception handling with
std::bad_alloc:try-catchblock catches memory allocation exceptions- Provides detailed error information
- Prevents program crash
std::nothrowallocation:- Prevents exception throwing
- Returns
nullptron allocation failure - Allows silent failure handling
Compile and run the program:
g++ memory_allocation.cpp -o memory_allocation
./memory_allocation
Example output:
Demonstrating memory allocation failure handling:
Memory allocation failed: std::bad_alloc
Safe memory allocation successful
Important memory allocation strategies:
- Always check for allocation failures
- Use exception handling or
std::nothrow - Implement fallback mechanisms
- Avoid allocating extremely large memory blocks
Summary
In this lab, you will learn how to handle dynamic memory allocation in C++. First, you will learn to use the new operator to dynamically allocate memory at runtime, allowing for more flexible memory management compared to static allocation. Then, you will learn how to properly release this dynamically allocated memory using the delete operator to prevent memory leaks. Additionally, you will explore creating dynamic arrays with new[] and deleting them with delete[]. The lab also covers the use of smart pointers, such as shared_ptr and unique_ptr, to simplify memory management and avoid common pitfalls. Finally, you will learn techniques to check for and handle memory allocation failures.



