Use malloc vs new Allocators in C++

  1. Use the new Operator to Allocate Dynamic Memory in C++
  2. Use the new Operator and std::unique_ptr to Allocate Dynamic Memory in C++
  3. Use the malloc Function and realloc/reallocarray to Allocate Dynamic Memory

This article will explain several methods of using malloc vs new allocators in C++.

Use the new Operator to Allocate Dynamic Memory in C++

new is the preferred interface to manage dynamic memory in C++ directly. It constructs an object of the given type and returns the pointer to it. Objects allocated using the new operator are default initialized, meaning that built-in and compound type objects have garbage values that need to be initialized before use.

Note that new can be called with multiple notations to suit different needs, but in the following example, we allocate the int array of size 10. Thus, the returned pointer stored in the arr1 variable points to the chunk of memory that’s 40 bytes. initPrintIntVector function is only implemented to demonstrate a practical coding example better. Since we are using a so-called naked pointer, it’s important to free up the allocated memory with the delete operator before the program exits. Mind though, brackets after the delete is also necessary to deallocate each location in the array.

#include <iostream>
#include <vector>
#include <iomanip>
#include <random>

using std::cout; using std::vector;
using std::endl; using std::setw;

constexpr int SIZE = 10;
constexpr int NEW_SIZE = 20;
constexpr int MIN = 1;
constexpr int MAX = 1000;

void initPrintIntVector(int *arr, const int &size)
{
    std::random_device rd;
    std::default_random_engine eng(rd());
    std::uniform_int_distribution<int> distr(MIN, MAX);

    for(int i = 0; i < size; ++i) {
        arr[i] = distr(eng) % 1000;
        cout << setw(2) << arr[i] << "; ";
    }
    cout << endl;
}

int main()
{
    int *arr1 = new int[SIZE];

    initPrintIntVector(arr1, SIZE);

    delete [] arr1;
    return EXIT_SUCCESS;
}

Output (*random):

 8; 380; 519; 536; 408; 666; 382; 244; 448; 165;

Use the new Operator and std::unique_ptr to Allocate Dynamic Memory in C++

Even though the new operator seems to be a fine tool for dynamic memory allocation, it can get quite error-prone in the large codebases with intensive memory manipulations. Namely, deallocation of the memory resources at the right time is quite a hard problem to tackle, and mostly it results in memory leaks or unexpected run-time errors. That’s why the standard library, since the C+11 version added smart pointers that automatically delete the memory they point to. std::unique_ptr is one type of smart pointers, which allows only itself to point to the given object. Note that the allocation is still done using the new operator, and after the pointer has been used we can exit the program without calling to delete.

#include <iostream>
#include <vector>
#include <iomanip>
#include <random>

using std::cout; using std::vector;
using std::endl; using std::setw;

constexpr int SIZE = 10;
constexpr int NEW_SIZE = 20;
constexpr int MIN = 1;
constexpr int MAX = 1000;

void initPrintIntVector(int *arr, const int &size)
{
    std::random_device rd;
    std::default_random_engine eng(rd());
    std::uniform_int_distribution<int> distr(MIN, MAX);

    for(int i = 0; i < size; ++i) {
        arr[i] = distr(eng) % 1000;
        cout << setw(2) << arr[i] << "; ";
    }
    cout << endl;
}

int main()
{
    std::unique_ptr<int[]> arr2(new int[SIZE]);

    initPrintIntVector(arr2.get(), SIZE);

    return EXIT_SUCCESS;
}

Output:

985; 885; 622; 899; 616; 882; 117; 121; 354; 918;

Use the malloc Function and realloc/reallocarray to Allocate Dynamic Memory

On the other hand, the C++ code can call the original C-style allocator function - malloc, which is quite an archaic way of dynamic memory manipulation for modern C++ standards. It’s not the recommended way of allocating objects on the heap, but on the plus side, malloc provides more flexible functionality.

malloc is called with a single argument that specifies the sizeof object and it returns the void* which should cast to the corresponding type in C++. The one advantage of the malloc allocated memory is that it can be expanded/shrunk by the realloc or reallocarray function. The realloc function takes the pointer to the object and new size as the arguments, whereas reallocarray takes the pointer, the number of elements, and each element’s size. Note that if the object memory is expanded, the old stored values stay intact, and newly added elements are uninitialized. Thus, the following example prints out the expanded arr3 elements for demonstration purposes only, and it should not be the case in the real-world program.

#include <iostream>
#include <vector>
#include <iomanip>
#include <random>

using std::cout; using std::vector;
using std::endl; using std::setw;

constexpr int SIZE = 10;
constexpr int NEW_SIZE = 20;
constexpr int MIN = 1;
constexpr int MAX = 1000;

void initPrintIntVector(int *arr, const int &size)
{
    std::random_device rd;
    std::default_random_engine eng(rd());
    std::uniform_int_distribution<int> distr(MIN, MAX);

    for(int i = 0; i < size; ++i) {
        arr[i] = distr(eng) % 1000;
        cout << setw(2) << arr[i] << "; ";
    }
    cout << endl;
}

void printIntVector(int *arr, const int &size)
{
    for(int i = 0; i < size; ++i) {
        cout << setw(2) << arr[i] << "; ";
    }
    cout << endl;
}

int main()
{
    int *arr3 = static_cast<int *>(malloc(SIZE * sizeof(int)));
//    int *arr3 = static_cast<int *>(malloc(SIZE * sizeof *arr3));
//    int *arr3 = static_cast<int *>(malloc(sizeof(int[SIZE])));

    initPrintIntVector(arr3, SIZE);

    arr3 = static_cast<int *>(reallocarray(arr3, NEW_SIZE, sizeof(int)));
    // arr3 = static_cast<int *>(realloc(arr3, NEW_SIZE * sizeof(int)));
    printIntVector(arr3, NEW_SIZE);

    free(arr3);
    return EXIT_SUCCESS;
}

Output:

128; 346; 823; 134; 523; 487; 370; 584; 730; 268;
128; 346; 823; 134; 523; 487; 370; 584; 730; 268;  0;  0;  0;  0;  0;  0;  0;  0;  0;  0;