Problems with Pointers
Learn how to address the problems that occur while using pointers.
Pointers are a powerful tool in programming languages like C and C++, allowing for efficient memory management and data manipulation. However, they can also introduce various problems if not used correctly. In this discussion, we’ll explore some common problems related to pointers, along with examples and figures where applicable.
Dangling pointer
A dangling pointer poses a very serious concern as it occurs when a pointer points to memory that has been deleted or is no longer valid. This can result in unpredictable behavior, crashes, and other unintended consequences during program execution and should be handled carefully to prevent adverse effects on the program’s functionality. The given illustration describes the problem of the dangling pointer in which the pointer d
is a dangling pointer.
Let’s look at an example that demonstrates the use of dynamic memory allocation for character arrays and highlights the issue of dangling pointers.
#include <iostream>using namespace std;int main(){char * ptr = new char[6] {'D', 'A','V', 'I', 'D'}; // will initialize to 'D', 'A', 'V', 'I', 'D', '\0'cout << "The content of ptr array is: " <<ptr <<'\n';char * d;d = ptr;d[0] = 'M';cout <<"ptr : "<< ptr<<'\n';cout <<" d : "<< d<<'\n';delete []ptr; // Will delete the memory pointed by ptr on heapptr = nullptr; // d is a dangling pointerchar* ptr2 = new char[5] {'M', 'I','K', 'E'}; // most probably MIKE will be overwritten on DAVIDd[0] = 'T'; // d is still pointing to the same location hence any change through d might affect ptr2's memory, which has nthg to do with dcout <<" d : "<< d <<'\n'; // pointer d is danglingreturn 0;}
In the given code, a dangling pointer issue occurs when
delete[]
is used to deallocate the memory pointed to byptr
, butd
still retains the address of the deallocated memory. This makesd
a dangling pointer, which can lead to unpredictable behavior when accessed. When creatingptr2
, it’s possible that it may reuse the same memory previously used byptr
, causing{'M','I','K', 'E', '\0'}
to overwrite{'D','A','V','I','D','\0'}
. Modifyingd[0]
after deallocatingptr
can result in undefined behavior sinced
is now pointing to an invalid memory address. In this case, when we run the code, it printsd : TIKE
.
To avoid such problems, it’s essential to handle pointers carefully and set them to nullptr
after deallocation. Proper memory management is crucial to ensure stable and reliable code execution.
Fixing the dangling pointer: Deep copying the memory
Shallow copying is when two pointers start pointing to one location. One possible resolution for such an issue is to create a deep copy of the data. Instead of simply copying the address present in ptr
to d
, we allocate separate memory for d
and copy the content pointed to by ptr
to d
. By doing so, ptr
and d
will have their own independent memory locations, eliminating the risk of dangling pointers and ensuring that each pointer points to valid and distinct memory blocks. The given illustration clearly describes the solution to a dangling pointer problem.
#include <iostream>#include <string.h>using namespace std;int main(){char * ptr = new char[6] {'D', 'A','V', 'I', 'D'};cout << "The content of msg0 array is: " <<ptr <<'\n';char * d = new char[strlen(ptr)+1]; // strlen(ptr) returns 5// (it returns string length excluding '\0')for(int i = 0; i < strlen(ptr); i++)d[i] = ptr[i];cout <<"After copying \n";cout <<"ptr : "<< ptr<<'\n';cout <<" d : "<< d<<'\n';delete []ptr; // Will delete the memory pointed by ptr on heapcout <<"ptr : "<< ptr<<'\n';cout <<" d : "<< d<<'\n';return 0;}
In the code,
ptr
is initially allocated on the heap and assigned the values{'D', 'A', 'V', 'I', 'D'}
. To avoid a dangling pointer, a new pointerd
is created with a new memory block of the appropriate size, calculated asstrlen(ptr)+1
. Then, a loop is used to copy each character fromptr
tod
, creating a deep copy of the original content. By doing so,d
now holds its own independent memory location with the same content asptr
. After the deep copy is made,ptr
can be safely deallocated usingdelete[]
, releasing the memory it was pointing to. However, the copy of the original content is preserved ind
, ensuring that it remains a valid and accessible pointer. The code demonstrates a way to save the content from a potentially dangling pointer by creating a new pointer and a deep copy, thus avoiding undefined behavior when accessing or using the pointer later.
By creating a deep copy, the code eliminates the dangling pointer issue. Now, d
holds a valid memory location with its own independent copy of the original content, allowing both pointers to be used independently without interfering with each other’s memory. This ensures proper memory management and avoids potential undefined behavior associated with dangling pointers.
Memory leakage
Dynamic memory allocation using the new
operator requires explicit deletion with the delete
operator to avoid memory leaks, where memory gradually accumulates and causes a detrimental impact on program performance. Failing to properly deallocate memory can result in memory saturation, leading to persistent memory leaks that can damage program execution and degrade system stability. It is crucial to ensure responsible memory management by diligently releasing dynamically allocated memory to mitigate the risks of memory leakage and ensure optimal program performance.
In the example discussed above, we addressed the issue of dangling pointers, but unfortunately, it resulted in a new problem: memory leakage. This occurred because when we copied the content of ptr
to d
, we allocated new memory for d
without releasing the memory it was previously pointing to. As a result, we lost the reference to that memory block, leading to a memory leak. This can cause inefficiencies in memory usage and can lead to performance issues in the long run if not properly addressed. To solve this issue, we need to free the allocated memory before assigning d
to another allocated memory. The following illustration describes the solution.
Here’s the code for solving the memory leakage issue:
#include <iostream>#include <string.h>using namespace std;int main(){char * ptr = new char[6] {'D', 'A','V', 'I', 'D'};cout << " ptr: " << ptr <<'\n';ptr = new char[10]{'H', 'E','L', 'L', 'O',' ', 'C', '+', '+', '\0'};cout << " ptr: " << ptr <<'\n';return 0;}
The code in the
MemeoryLeakage.cpp
has a memory leakage issue. It allocates memory on the heap for the character array'DAVID'
usingnew
, but fails to deallocate this memory before assigning a new memory block to the same pointer,ptr
. As a result, the previously allocated memory is lost and can’t be freed, leading to a memory leak. The subsequent allocation of memory for the character array'HELLO C++'
overrides the previous memory block without releasing it, exacerbating the memory leakage problem. To prevent memory leaks, it is necessary to release the previously allocated memory usingdelete[]
before assigning a new memory block to the pointer.Line 9 in the
TheFix.cpp
file fixes this issue.
Null pointer/illegal address dereferencing
Another potential issue that can arise with dynamic memory allocation is the use of null pointers or illegal memory dereferencing. A null pointer is a pointer that doesn’t point to a valid memory location. Dereferencing a null pointer or attempting to access memory using an illegal address can lead to runtime errors and program crashes.
Similarly, if a pointer is not initialized or explicitly set to a valid memory address, dereferencing it can lead to undefined behavior. It is essential to initialize pointers properly and ensure that they point to valid memory locations before dereferencing them.
#include <iostream>using namespace std;int main(){int* ptr;// Dereferencing a pointer with a garbage address will lead to undefined behavior// *ptr = 10; // uncomment it to check (the program will crash)ptr = nullptr;// Dereferencing a null pointer// This will also lead to undefined behavior// *ptr = 10; // uncomment it to check (the program will crash)ptr = new int;// Check if memory allocation was successfulif (ptr != nullptr){*ptr = 42;cout << "Dynamically allocated value: " << *ptr << endl;}// Deallocate the memorydelete ptr;ptr = nullptr;// Accessing the memory after deallocation// This should not be executedif (ptr != nullptr){cout << "This should not be executed.";}return 0;}
Deleting stack memory
Deleting stack memory refers to attempting to deallocate memory that was allocated on the stack, which is not allowed. Only memory allocated on the heap using new
can be deallocated using delete
.
#include <iostream>using namespace std;int main(){int x = 5;int* ptr = &x;cout << "x: "<< *ptr << endl;// delete ptr; // Logical Error: Invalid deallocation of stack memoryint A[] = {1,2,3,4,5};int size = sizeof(A)/sizeof(int);ptr = A;cout << "A = { ";for(int i=0;i<size; i++)cout << ptr[i] << " ";cout << "}";// delete [] ptr; // Logical Error: Invalid deallocation of stack memoryreturn 0;}
In the above code, there are attempts to delete stack memory using the delete
operator, which leads to logical errors.
When a variable is declared within a function, it is allocated memory on the stack. In the code, a pointer is assigned the address of a stack variable using the address-of operator (
&
). However, callingdelete
on this pointer is incorrect becausedelete
is used to deallocate memory that was dynamically allocated on the heap usingnew
. Stack memory is automatically managed by the compiler, and there is no need to explicitly deallocate it usingdelete
.Similarly, when an array is declared within a function, the memory for the array is allocated on the stack. Assigning the array’s address to a pointer and then calling
delete[]
on that pointer is also incorrect.delete[]
is used to deallocate dynamically allocated arrays on the heap, not stack-allocated arrays. Stack memory is automatically released when the scope of the variable ends, and attempting to delete stack memory explicitly can result in undefined behavior and program crashes.
In both cases, trying to delete stack memory using delete
or delete[]
is a logical error because stack memory is managed by the compiler, and manual deallocation is unnecessary.
Exercise: Identify the error
There are several variants of the errors mentioned above. We’ll discuss them as we proceed in the later part of the course.
As a practice exercise, try to find the bug in the following program.
If you have trouble getting to the solution, you can click the “Solution” button to see how to solve the problem.
#include <iostream>using namespace std;int main(){int* ptr = new int(5);cout << "Before deleting => Value: "<<*ptr;delete ptr;cout << " After deleting => Value: "<<*ptr;delete ptr;return 0;}