myNew

Overloading Operator new and delete 1

It happens quite too often that a C++ application allocates memory but doesn’t deallocate it. This is the job of the operator to new and delete. Thanks to them both, you can explicitly manage the memory management of an application.

 

Occasionally, I had to verify that a customer’s application correctly releases its memory. In particular, programs running for an extended period and often allocating and deallocating memory are challenging from the memory management perspective. Of course, the automatic memory release during the program’s shutdown is not an option.

The baseline

As a baseline of my analysis, I use a simple program that often allocates and deallocates memory. In production, the code is bigger. That does not affect my strategy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// overloadOperatorNewAndDelete.cpp

// #include "myNew.hpp"
// #include "myNew2.hpp"
// #include "myNew3.hpp"

#include <iostream>
#include <string>

class MyClass{
  float* p= new float[100];
};

class MyClass2{
  int five= 5;
  std::string s= "hello";
};


int main(){
    
    int* myInt= new int(1998);
    double* myDouble= new double(3.14);
    double* myDoubleArray= new double[2]{1.1,1.2};
    
    MyClass* myClass= new MyClass;
    MyClass2* myClass2= new MyClass2;
    
    delete myDouble;
    delete [] myDoubleArray;
    delete myClass;
    delete myClass2;
    
//  getInfo();
    
}

 

The key question is. Is there a corresponding delete for each new call?

Too careless

The question is quite simple to answer by overloading the global operator new and delete. I count for each operator, how often it was called.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    Operator new

    C++ offers the operator new in four variations.

    void* operator new  (std::size_t count );
    void* operator new[](std::size_t count );
    void* operator new  (std::size_t count, const std::nothrow_t& tag);
    void* operator new[](std::size_t count, const std::nothrow_t& tag);
    

     

    The first two variations will throw a std::bad_alloc exception if they can not provide the memory. The last two variations return a null pointer. It’s quite convenient that is sufficient to overload only version 1 because versions 2 – 4 use the version 1: void* operator new(std::size_t count). This statement also holds for variants 2 and 4, designed for C arrays. You can read the details of the global operator new here: cppreference.com.

    The statements also hold for operator delete.

    Operator delete

     C++ offers six variations for operator delete.

    void operator delete  (void* ptr); 	
    void operator delete[](void* ptr);
    void operator delete  (void* ptr, const std::nothrow_t& tag);
    void operator delete[](void* ptr, const std::nothrow_t& tag);
    void operator delete  (void* ptr, std::size_t sz);
    void operator delete[](void* ptr, std::size_t sz);
    

     

    According to operator new, it is sufficient to overload for operator delete the first variant because the remaining five use void operator delete(void* ptr) as a fallback. Only a word about the two last versions of operator delete. You have in this version the length of the memory block in the variable sz  to your disposal. Read the details here at cppreference.com.

    This time I use the program overloadOperatorNewAndDelete.cpp the header myNew.hpp (line 3). The same holds for line 34. Here I invoke the function getInfo to get information about my memory management.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // myNew.hpp
    
    #ifndef MY_NEW
    #define MY_NEW
    
    #include <cstdlib>
    #include <iostream>
    #include <new>
    
    static std::size_t alloc{0};
    static std::size_t dealloc{0};
    
    void* operator new(std::size_t sz){
        alloc+= 1;
        return std::malloc(sz);
    }
    
    void operator delete(void* ptr) noexcept{
        dealloc+= 1;
        std::free(ptr);
    }
    
    void getInfo(){
        
        std::cout << std::endl;
     
        std::cout << "Number of allocations: " << alloc << std::endl;
        std::cout << "Number of deallocations: " << dealloc << std::endl;
        
        std::cout << std::endl;
    }
    
    #endif // MY_NEW
    

     

    I create in the file two static variables alloc and dealloc (lines 10 and 11). They keep track of how often I used the overloaded operator new (line 13) and operator delete (line 18). I delegate in the functions the memory allocation to std::malloc and the memory deallocation to std::free. The function getInfo (lines 23 – 31) provides me with the numbers and displays them.

    The question is. Have I cleaned everything clean?

     myNew

    Of course, not. That was the intention of this and the following post. Now I know that I have leaks. Maybe it helps to determine the addresses of the objects which I have forgotten to clean up.

    Addresses of the memory leaks.

    So, I have to put more cleverness into the header myNew2.hpp.

     

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // myNew2.hpp
    
    #ifndef MY_NEW2
    #define MY_NEW2
    
    #include <algorithm>
    #include <cstdlib>
    #include <iostream>
    #include <new>
    #include <string>
    #include <array>
    
    int const MY_SIZE= 10;
    
    std::array<void* ,MY_SIZE> myAlloc{nullptr,};
        
    void* operator new(std::size_t sz){
        static int counter{};
        void* ptr= std::malloc(sz);
        myAlloc.at(counter++)= ptr;
        return ptr;
    }
    
    void operator delete(void* ptr) noexcept{
        auto ind= std::distance(myAlloc.begin(),std::find(myAlloc.begin(),myAlloc.end(),ptr));
        myAlloc[ind]= nullptr;
        std::free(ptr);
    }
    
    void getInfo(){
        
        std::cout << std::endl;
         
        std::cout << "Not deallocated: " << std::endl;
        for (auto i: myAlloc){
            if (i != nullptr ) std::cout << " " << i << std::endl;
        }
        
        std::cout << std::endl;
        
    }
    
    #endif // MY_NEW2
    

     

    The key idea is to use the static array myAlloc (line 15) to keep track of the addresses of all std::malloc (line 19) and std::free (line 19) invocations. Of course, I can not use the function operator new container that needs dynamic memory. This container would invoke the operator new. A recursion that would cause my program to crash. Therefore, I use a std::array in line 15 because std::array gets its memory at compile time. Now it can happen that my std::array becomes too small. Therefore, I invoke myAlloc.at(counter++) to check the array boundaries.

    Which memory address have I forgotten to release? The output answers.

    myNew2

    A simple search for the object having the address is no good idea. Because it is quite probable that a new call of std::malloc reuses an already-used address. That is ok if the objects have been deleted in the meantime.

    But why are the addresses part of the solution? I have only to compare the memory address of the created objects with those of the not deleted objects.

    Comparison of the memory addresses

    In addition to the memory address, I have the reserved memory size at my disposal. I use this information in operator new.

     

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // myNew3.hpp
    
    #ifndef MY_NEW3
    #define MY_NEW3
    
    #include <algorithm>
    #include <cstdlib>
    #include <iostream>
    #include <new>
    #include <string>
    #include <array>
    
    int const MY_SIZE= 10;
    
    std::array<void* ,MY_SIZE> myAlloc{nullptr,};
        
    void* operator new(std::size_t sz){
        static int counter{};
        void* ptr= std::malloc(sz);
        myAlloc.at(counter++)= ptr;
        std::cerr << "Addr.: " << ptr << " size: " << sz << std::endl;
        return ptr;
    }
    
    void operator delete(void* ptr) noexcept{
        auto ind= std::distance(myAlloc.begin(),std::find(myAlloc.begin(),myAlloc.end(),ptr));
        myAlloc[ind]= nullptr;
        std::free(ptr);
    }
    
    void getInfo(){
        
        std::cout << std::endl;
         
        std::cout << "Not deallocated: " << std::endl;
        for (auto i: myAlloc){
            if (i != nullptr ) std::cout << " " << i << std::endl;
        }
        
        std::cout << std::endl;
        
    }
    
    #endif // MY_NEW3
    

     

    Now, the allocation and deallocation of the application are more transparent.

    myNew3

    A simple comparison shows. I forget to release an object with 4 bytes and an object with 400 bytes. In addition, the allocation sequence in the source code corresponds to the sequence of outputs in the program. Now, it should be quite easy to identify the missing memory releases.

    What’s next?

    The program is not beautiful in two ways. First, I statically allocate the memory for std::array; second, I want to know which object was not released. In the next post, I will solve both issues.   

     

     

     

     

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *