1 Understanding Memory Allocation
Memory allocation refers to the process of reserving a block of memory for storing data during program execution. Unlike static memory allocation, where memory is allocated at compile time, dynamic memory allocation allows memory to be allocated and deallocated at runtime. This flexibility is particularly useful when dealing with data structures of varying sizes or lifetimes.
2 Dynamic Memory Allocation Functions
In C++, memory allocation is typically performed using functions such as malloc(), calloc(), and realloc(). These functions allocate memory on the heap, returning a pointer to the allocated memory block. Let's briefly explore each of these functions:
malloc
:
This function is used to allocate a block of memory of a specified size in bytes. It returns a pointer to the beginning of the allocated memory block, or NULL in C and nullptr in C++ if the allocation fails.
- The malloc function stands for “memory allocation”.
- It is used to allocate a block of memory of a specified size in bytes.
- The function signature is:
void* malloc(size_t size);
- It takes a single argument size, which specifies the number of bytes to allocate.
- It returns a pointer to the allocated memory block, or nullptr in C++ || NULL in C, if the allocation fails (e.g., due to insufficient memory). It returns pointer of type
void*
which means it is a generic pointer that can be implicitly converted to any other pointer type. It allows you to assign the returned pointer to a pointer of any desired type. - The memory allocated by malloc is uninitialized, meaning it contains arbitrary values.
#include <bits/stdc++.h>
using namespace std;
int main() {
int* ptr = static_cast<int*>(malloc(sizeof(int)));
if (ptr != nullptr) {
*ptr = 42;
}
cout << *ptr << endl;
return 0;
}
// C++ way of converting void* to int*
int* ptr = static_cast<int*>(malloc(sizeof(int)));
// C way of converting void* to int*
int* ptr = static_cast<int*>(malloc(sizeof(int)));
calloc
:
Similar to malloc(), calloc() allocates memory for an array of elements, initializing all bytes in the allocated memory to zero. It takes two arguments: the number of elements to allocate and the size of each element in bytes.
- The calloc function stands for “contiguous allocation”.
- It is used to allocate a block of memory for an array of elements, with each element initialized to zero.
- The function signature is:
void* calloc(size_t num_elements, size_t element_size);
- It takes two arguments: num_elements, which specifies the number of elements to allocate, and element_size, which specifies the size of each element in bytes.
- It returns a pointer to the allocated memory block, or nullptrin C++ || NULL in C, if the allocation fails.
- Unlike malloc, calloc initializes the allocated memory to zero.
#include <bits/stdc++.h>
using namespace std;
int main() {
int* arr = static_cast<int*>(calloc(5, sizeof(int)));
if (arr != nullptr) {
// The memory is initialized to zero
// arr[0] = 0, arr[1] = 0, ..., arr[4] = 0
int* ptr = arr;
for (int i = 0; i < 5; i++) {
ptr[i] = i;
}
for (int i = 0; i < 5; i++) {
cout << arr[i] << endl;
}
}
return 0;
}
Similar to malloc
, calloc
also returns void*
, which is a generic pointer and can be casted to any type.
In C++, you would use a C++ style cast:
int* ptr = static_cast<int*>(malloc(sizeof(int)));
In C, you would use a C style cast:
int* ptr = (int*)malloc(sizeof(int));
realloc()
:
This function is used to resize a previously allocated memory block. It takes two arguments:
a pointer to the previously allocated memory block and the new size of the block.
If resizing is successful, realloc() returns a pointer to the beginning of the resized block. If the allocation fails, NULL is returned in C || nullptr in C++, and the original block remains unchanged.
- The realloc function stands for “re-allocation.”
- It is used to resize an existing block of memory previously allocated with malloc, calloc, or realloc.
- The function signature is:
void* realloc(void* ptr, size_t new_size);
- It takes two arguments: ptr, which is a pointer to the existing memory block, and new_size, which specifies the new size of the memory block in bytes.
- It returns a generic pointer (
void*
) pointer to the reallocated memory block, which may be the same as the original pointer or a new pointer, depending on whether the memory needs to be moved to accommodate the new size. If the reallocation fails, it returns nullptr in C++ || NULL in C, and the original memory block remains unchanged. - If ptr is nullptr, realloc behaves like malloc.
- If new_size is smaller than the original size of the memory block, the excess bytes are truncated.
- If new_size is larger than the original size of the memory block, the additional bytes are uninitialized.
#include <bits/stdc++.h>
using namespace std;
int main() {
int* ptr = static_cast<int*>(malloc(sizeof(int)));
if (ptr != nullptr) {
*ptr = 42;
ptr = static_cast<int*>(realloc(ptr, 2 * sizeof(int)));
if (ptr != nullptr) {
// Memory was reallocated successfully
// ptr[0] is still 42, ptr[1] is uninitialized
}
}
return 0;
}
3 Operator new, new[] and delete, delete[] (Native to C++)
In addition to memory allocation functions, C++ provides operators new and delete for dynamic memory allocation and deallocation. These operators are more convenient and intuitive to use than their function counterparts. Let's explore how these operators work:
new Operator:
The new operator is used to dynamically allocate memory for a single object. It returns a pointer to the allocated memory. After allocating memory, it also calls the constructor of the object (if any) to initialize it.
- It allocates memory for a single object.
int* p = new int;
In this example, new int allocates memory for a single integer and returns a pointer to the allocated memory.
new[]:
The new[] operator is used to dynamically allocate memory for an array of objects. It returns a pointer to the allocated memory. After allocating memory, it also calls the constructors of the objects (if any) to initialize them.
- It allocates memory for an array of objects.
int* arr = new int[10];
delete Operator:
The delete operator is used to deallocate memory allocated using the new operator. It releases the memory allocated for a single object or an array of objects, optionally invoking the object's destructor to perform cleanup operations.
int* p = new int;
// Use p...
delete p;
When delete is called, it performs the following actions:
- Calls the destructor of the object pointed to by pointer.
- Deallocates the memory previously allocated by new.
delete[]:
This operator is used to destroy an array of dynamically allocated objects and deallocate the memory associated with the array. It is typically used when the array was allocated using the new[]
operator.
The syntax for delete[]
is:
delete[] pointer;
Example:
int* arr = new int[10];
// Use arr...
delete[] arr;
When delete[]
is called, it performs the following actions:
- Calls the destructor of each element in the array pointed to by
pointer
, in reverse order of construction. - Deallocates the memory block previously allocated by
new[]
.
4 Allocating Memory in C++
In practice, memory allocation in C++ is often performed using the new operator for object allocation and the delete operator for deallocation. These operators provide a convenient and type-safe way to manage dynamic memory, automatically handling the allocation and deallocation of memory blocks.
// Using new operator to allocate memory for a single object
int* ptr = new int;
*ptr = 42;
// Using new operator to allocate memory for an array of objects
int* arr = new int[10];
arr[0] = 1;
arr[1] = 2;
// ...
// Deallocating memory using delete operator
delete ptr;
delete[] arr;
Difference between new and malloc
The new
operator and the malloc
function in C++ are both used for dynamic memory allocation, but they have some important differences:
Type Safety:
- new: The new operator is type-safe and automatically calculates the size of the object being allocated.
- malloc: The malloc function is not type-safe and requires the programmer to manually specify the size of the memory block in bytes.
Initialization:
- new: When using new, constructors are automatically called for objects being allocated, ensuring that the memory is properly initialized.
- malloc: Memory allocated using malloc is uninitialized, meaning it contains arbitrary values. Initialization must be performed manually if necessary.
Return Type:
- new: The new operator returns a pointer to the allocated object with the correct type.
- malloc: The malloc function returns a void* pointer, which must be explicitly cast to the desired type.
Size Calculation:
- new: The size of the memory block allocated by new is automatically calculated based on the size of the object's type.
- malloc: The size of the memory block allocated by malloc must be manually calculated and specified in bytes.
Operator Overloading:
- new: The new operator can be overloaded to customize memory allocation behavior for user-defined types.
- malloc: The malloc function cannot be overloaded.
Exception Handling:
- new: If memory allocation fails, the new operator throws a std::bad_alloc exception by default. Exception handling can be used to gracefully handle allocation failures.
- malloc: If memory allocation fails, the malloc function returns nullptr. Error checking must be performed manually to detect allocation failures.
Compatibility:
- new: The new operator is specific to C++ and is not available in C.
- malloc: The malloc function is part of the C standard library and can be used in both C and C++.