The new and delete Operators

Dynamic memory allocation with the new and delete operators in C++. Discussion on heap allocations for classes having no default constructor. We also explain how to deal with cases when heap allocations fail, and about allocations of zero sized objects, and customizing these operators.

Last Reviewed and Updated on February 7, 2020
Posted by Parveen(Hoven),
Aptitude Trainer and Software Developer

My C/C++ Videos on Youtube

Here is the complete playlist for video lectures and tutorials for the absolute beginners. The language has been kept simple so that anybody can easily understand them. I have avoided complex jargon in these videos.

Where do global variables live? Where do stack variables live? Where exists dynamic memory? I do not want to leave you with a vague, hazy idea about answers to these questions. An understanding of the underlying things always pays; it at least serves to quench the thirst of curiosity. A worldly-wise developer is usually content with the layer of abstraction. But if you have un-answered questions, and, of course, time to find answers to them, then here's a simple explanation.

Where does Memory come from?

All memory, whether stack, global, or heap is borrowed from the operating system, and it has to be - your application is hosted by the OS, and the OS is the overall monitor of everything that is happening there. When your application is launched, a request is sent to the OS for memory allocation that is the aggregate sum of requirements for laying program code[.rodata], for laying static global variables[data segment], for laying strings[.text], plus the requirements for use of stack variables. The above are all known in advance. The OS allocates memory to the application along with a contiguous chunk for use as a stack memory. An extra amount of memory is also obtained and called the heap memory. Please note: what I have written is a very general and simplified model, actual things depend on many various factors, like the CPU, it's manufacturer, and so on.

So the vacant memory slots are obtained from the OS just before the launch of your executable. These are filled, and used as and when the need arises. The program code, the globals and the string constants are immediately laid, and the vacant slots for stack memory are used to push and pop the stack variables as and when the functions are called, and exited. By default, each thread can use upto 1MB stack memory[Microsoft Visual Studio Linker], but this limit can be configured. For Linux based systems, this limit is determined by environment variables. It is architecture dependent. But it does have finite limits. Stack memory is limited - all your quota is made available to you at the start of your process. You can't ask for it afterwards.

Consider the code below. Don't run it! It's a faulty code, where a function calls itself, and the calls keep on winding round and round. On each call two int variables are created on the stack memory. Very soon, the function fx will have created enough stack variables to exhaust its quota of stack memory. With no more memory a dead end is reached. This point of no return brings the program to a grinding halt. This condition is commonly called stack overflow.

void fx(int p)
{

    int i = 78;

    int j = p;

    cout << (i + j)<< endl;

    fx(i + j);

}

int main()
{

    fx(9);

    return 0;

}


Why is stack memory so scarce, and why can't it be expanded at runtime? Stack memory is a set of serially arranged memory blocks. Variables are easily created and access is fast because of the way stacks work. The microprocessor stack pointer needs to be incremented and decremented to create and release a stack variable, a process that boils down to an increment/decrement operation requiring only a couple of machine cycles. An OS could physically allocate stack memory in the CPU cache itself, making it even faster.

Dynamic Memory

It is not always possible to accurately estimate memory requirements when an application is being compiled. For example, it's impossible to know in advance the number of paragraphs a typist is going to type. Approximations and averages don't succeed here. You cannot make a design time allocation of 10000 paragraphs taking that figure to be an infinity. What if most of your users are going to type not more than two or three paragraphs - why should their machines be taxed with a capacity of 10000 paragraphs?

The solution to this problem lies in obtaining memory from the OS as and when a need arises, rather than ask for a big blob at application startup. Such memory is called heap memory. There is no sequential push and pop involved in obtaining and clearing the heap memory. Heap memory can be allocated anytime, it can be released anytime. It is a shared memory - shared by all the threads of an application. A program has one heap, but each thread of a program has its own stack.

Borrowing Memory from the Heap

Bytes for a heap allocation have to be obtained from the operating system which provides APIs for this purpose. Using those APIs is one way. Another way is to use the C functions calloc, malloc, etc., which, in turn communicate with the OS. The use of the C functions is platform independent so you are not required to know the platform specific API.

Another alternative method of borrowing memory from the heap is to use the C++ operator called new. This operator allocates the requested number of bytes on the heap, and initializes them to the requested type of object or data. Then it returns a typed pointer to the allocation. In simple language, if you want an int on the heap, the new operator will allocate 4 bytes[assuming that an int needs 4 bytes] on the heap, and then return an int* to you. In general, if you request a heap allocation for type T you will get back a T*. Here's an example.

int main()
{

    int *pi = new int;

    // put 7 into that int
    *pi = 7;

    ....
}


An array can also be requested from the heap. In this case also, the new operator returns a T* to you. Below is an example of obtaining an array of class objects on the heap. The class has a default constructor that prints a message, so that we can know that the new operator has created and initialized the objects for us.

class CAbc
{

public:
    CAbc()
    {

        cout << "ctor called!" << endl;

    }

};

int main()
{

    cout << "How many objects: ";

    int num;

    cin >> num;

    CAbc *arr = new CAbc[num];

    ....
    return 0;

}


Two points must be noted here. First, the heap allocation through the new operator is not a raw allocation unlike the void * returned by the C functions malloc and calloc. You not only get back a pointer of the type T, but an object is also laid out on that memory - up and running - through the proper constructor call. The second thing is that the size of the array can be determined at run time. If you remember, the size of an array has to be definitely known at design time if that array is on the stack. Following is an error, as we already know.

int main()
{

    cout << "How many objects: ";

    int num;

    cin >> num;

    // ERROR, num must
    // be a compile time constant
    CAbc arr[num];

    return 0;

}


So the benefits of using the new operator are:

  1. Objects are already properly created for us - through the default constructor. Your class must have a default constructor if you want an array to be allocated. Please refer the latest C++ standard for more options on heap allocations for an array of a class that doesn't have a default constructor. Possibilities also exist with providing your own overload for the new operator, and also through the use of placement new operator.
  2. The number of objects need not be known in advance. They can be obtained by taking an input from the user.

Heap and Pointer to Pointer

As we have already seen above, that an array of class objects cannot be created on the heap unless it has a default constructor. Let me show you a common hack, in case your class doesn't have a default constructor. Suppose the following is our class.

class CAbc
{

    int y;

public:
    CAbc(int x) : y(x)
    {

        cout << "non-default ctor called!" << endl;

    }

};


The first step is to obtain an allocation of pointers for CAbc. That would be possible because we are not asking the new operator to create objects for us. We are asking a heap allocation for CAbc* - pointers of CAbc - i.e., we are asking for something where we would store addresses of objects of CAbc. In the second step run a loop to create your objects one by one as shown below.

class CAbc
{

    int y;

public:
    CAbc(int x) : y(x)
    {

        cout << "non-default ctor called: ";

        cout << x << endl;

    }

};

int main()
{

    cout << "How many objects: ";

    int num;

    cin >> num;

    CAbc ** ppt = new CAbc*[num];

    for (int x = 0; x < num; x++)
    {

        cout << "Enter ctor parameter(int): ";

        int y;

        cin >> y;

        ppt[num] = new CAbc(y);

    }

    ....
    return 0;

}


Allocations can Fail

A heap allocation can fail if the OS is low on memory and cannot meet your requirements. What happens then? The C++ standard behavior is that an exception is thrown by the new operator. The developer has to catch that exception, and take any corrective action, or exit gracefully. Following is a code that recklessly obtains allocations from the heap. There is surely going to be a failure point. Run this code to see it in action.

int main()
{

    // bad code. for demonstration only
    while(1)
    {

        try
        {

            int* i = new int[100000000];

        }

        catch(std::bad_alloc&)
        {

            // take corrective action
            // or fail gracefully
            cout << "Insufficient memory" << endl;

            return -1;

        }

    }

    return 0;

}


The exception above can be suppressed also. It can be prevented totally by allocating with nothrow new. If failure occurs, the new(nothrow) operator returns a null pointer, containing zero. So, instead of using the try catch blocks, a simple null test can be performed and corrective action taken. Shown below is an example.

int main()
{

    // bad code for demonstration only
    while(1)
    {

        int* i = new(nothrow) int[100000000];

        if(NULL == i)
        {

            // take corrective action
            // or fail gracefully
            cout << "Insufficient memory" << endl;

            return -1;

        }

    }

    return 0;

}


It is even possible to attempt a corrective action in another way - by using the function _set_new_handler. This function is used to hook your own custom function into the new operator. This function must return an int, and accept a parameter of type size_t[According to the 1999 ISO C standard (C99), size_t is an unsigned integer type of at least 16 bit (see sections 7.17 and 7.18.3).]. When a failure occurs, your function is called and the number of bytes you requested is passed to the function as an argument. Inside that function you can take a corrective action such as freeing up memory by deleting dispensable objects. This is a sort of a garbage collection that you could do. The next thing depends on what your function returns. If you return a non-zero number, then the runtime will make a new attempt at allocating memory. But, if you return zero, either an exception is thrown or a null is returned depending on the new you used. Have a look at this code.

#include <new.h>

int fx(size_t bytesRequested)
{

    // we shall try upto three failures
    static int maxAttempts = 3;

    cout << "Insufficient memory. " ;

    cout << "Could not allocate " ;

    cout << bytesRequested << " bytes" ;

    cout << endl;

    // free some memory
    // CODE HAS NOT BEEN SHOWN HERE
    cout << "freeing memory...";

    cout << "Attempts remaining: " << maxAttempts << endl;

    // the runtime will keep trying
    // till the return is a non-zero
    // number
    return maxAttempts--;

}

int main()
{

    _set_new_handler(fx);

    // bad code. for demonstration only
    while(1)
    {

        try
        {

            int* i = new int[100000000];

        }

        catch(std::bad_alloc&)
        {

            // exception is thrown when fx returns 0
            cout << "Giving up..." << endl;

            return -1;

        }

    }

    return 0;

}


Zero Sized Array on the Heap

It is possible to get an array of size zero on the heap. This is how.

int main()
{

    int* arr = new int[0];

    cout << arr << endl;

    // still required
    delete[]arr;

    return 0;

}


The return will be either zero[i.e., either arr will be zero] or repeated calls - to new int[0] - will yield a unique address each time. An allocation of zero size will have to be matched by a corresponding delete. You must be asking now: What's the whole point in this arrangement? These are the two primary reasons.

  1. It will allow the new operator to be implemented by using the C function malloc, and still maintain sense when zero sized allocation is requested, because malloc(0) is possible. According to the specifications, malloc(0) will return either "a null pointer or a unique pointer that can be successfully passed to free()". If the operator new is implemented through malloc, then the delete implemented through free() will still maintain compatibility. This is an extract from the standard.
    [32. The intent is to have operator new() implementable by calling malloc() or calloc(), so the rules are substantially the same. C++ differs from C in requiring a zero request to return a non-null pointer.]
  2. C++ in many ways ensures that distinct objects have unique addresses, even if they don't explicitly require storage. For example, an empty struct containing no data, just the braces, is possible even if it doesn't require any memory to be allocated, and yet, such a struct has a size of 1 byte. So it ensures a sort of compatibility with allocations on the stack.
  3. Zero sized pointers do have a use in generics where they can be used to compare object identity.

The delete Operator

The delete operator is used to release the memory allocated through the new operator. Every call to new must be paired with a call to delete so that heap allocations are returned back to the OS. If heap memory is not de-allocated, then a memory leak occurs. Future calls to new might fail. It also leads to a slowdown of the machine.

There is a special syntax for deleting an array allocation. Have a look at the syntax below.

int main()
{

    int * arr = new int[100];

    // release the entire array
    delete[] arr;

    return 0;

}


If you do not use those square brackets, the array won't get deleted.

Global new and delete Operators

When a type is created on the heap by using the new operator, the following sequence occurs.

  1. The global new operator is used to allocate memory for that type.
  2. If it is a class object, then the constructor for that object is called.

Similarly, when the delete operator is used, the following sequence occurs.

  1. If it is a class object, then the destructor for that object is called.
  2. The global delete operator is used to de-allocate memory for that type.

Substitutions are done by the compiler so that the above steps occur in the same sequence. These can be performed manually also. Like this.

class CMyClass
{

public:
    CMyClass()
    {

        cout << "ctor called!" << endl;

    }

public:
    ~CMyClass()
    {

        cout << "dtor called!" << endl;

    }

};

int main()
{

    // allocate memory
    CMyClass * p = (CMyClass*)::operator new(sizeof(CMyClass));

    // ctor call
    p = new(p) CMyClass;

    // call dtor
    p->~CMyClass();

    // release memory
    ::operator delete(p);

    return 0;

}


The above code might look a bit too complicated but it is using the global operators directly - something which is rarely done. There is global operator for every operator we know. For example, there is a global operator corresponding tp the plus/addition(+), which looks like ::operator+. We rarely use the global addition operator.The global new operator might use malloc, and the global delete operator might use free.

Customizing the delete and new Operators

It is possible to use your own allocation/de-allocation mechanism. This can be done by overloading the operators new and delete. This should be done by writing a class, and then overloading these operators. Whenever your main code calls new/delete operator for that class object, your custom code runs. Have a look at the code below. Run it to see all the messages as they appear. I have used the malloc/free mechanism for allocation/de-allocation but any imaginative use is possible, and it opens a fascinating world where you have your own memory store, and you can keep track of things at every stage, possibly defragmenting your memory in between.

class CMyClass
{

public:
    CMyClass()
    {

        cout << "ctor called!" << endl;

    }

public:
    ~CMyClass()
    {

        cout << "dtor called!" << endl;

    }

public:
    void *operator new(size_t requestedSize)
    {

        void* vp = malloc(requestedSize);

        cout << "Allocation done." << endl;

        return vp;

    }

    void operator delete(void *p)
    {

        free(p);

        cout << "De-Allocation done." << endl;

    }

    // more operators like new[] and
    // delete[] can also be overloaded
};

int main()
{

    CMyClass * vp = new CMyClass();

    delete vp;

    return 0;

}


The signature and returns of the new/delete operators should be taken not of. The runtime passes the requestedSize parameter to you. It is same as sizeof(T).

It is also possible to overload the global new and delete operators.

void * operator new(size_t reuestedSize) throw(std::bad_alloc)
{

}

void operator delete(void * p) throw()
{

}


There can be many reasons for overloading global new/delete operators. They help you to write your own logging messages so that debugging and optimization is possible in ways such as these.

  1. Alignment of memory blocks.
  2. Expose buffer over-runs.
  3. Detect use of un-initialized variables.
  4. Accounting

Destructor Skipping with delete

Calling delete on void* doesn't place a call for the destructor. Be careful! Figure out why the destructor won't run in the following case.

class CMyClass
{

public:
    CMyClass()
    {

        cout << "ctor called!" << endl;

    }

public:
    ~CMyClass()
    {

        cout << "dtor called!" << endl;

    }

};

int main()
{

    CMyClass * vp = new CMyClass();

    void* xp = vp;

    delete xp;

    return 0;

}


This can occur when casting is done on a pointer, and delete called on the pointer after casting. In such cases, a re_interpret cast would be required to get the things in order. Like below.

class CMyClass
{

public:
    CMyClass()
    {

        cout << "ctor called!" << endl;

    }

public:
    ~CMyClass()
    {

        cout << "dtor called!" << endl;

    }

};

int main()
{

    CMyClass * vp = new CMyClass();

    void* xp = vp;

    CMyClass* rp = reinterpret_cast<CMyClass*>(xp);

    delete rp;

    return 0;

}



Creative Commons License
This Blog Post/Article "The new and delete Operators" by Parveen (Hoven) is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Updated on 2020-02-07. Published on: 2015-12-15