Overloading Operator new and delete 1

Contents[Show]

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

 

From time to time I had to verify for a customer that its application correctly releases its memory. In particular, programs running for a long period of time and often allocate and deallocate memory are a challenge from the memory management perspective. Of course, the automatic release of the memory during the shutdown of the program is not an option.

The baseline

I use as baseline of my analysis a simple program that quite often allocates and deallocates memory. In production, the code is bigger. That has no effect on 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 to each new call?

To 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 .

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 the version 1 because the versions 2 - 4 use the version 1: void* operator new(std::size_t count). This statement holds also for the variants 2 and 4, which are designed for C arrays. You can read the details to the global operator new here: cppreference.com.

The statements hold also 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 5 use void operator delete(void* ptr) as 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 cppreference.com.

This time I use in the program overloadOperatorNewAndDelete.cpp the header myNew.hpp (line 3). The same holds for the lines 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 (line 10 and 11). They keep track 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 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 it 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 in the function operator new a container that needs dynamic memory. This container would invoke 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 to small. Therefore, I invoke myAlloc.at(counter++) for checking the array boundaries.

Which memory address have I forgotten to release? The output gives the answer.

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 reuse an already used address. That is ok, if the objects has 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 the memory address of the not deleted objects.

Comparison of the memory addresses

In addition to the memory address I have the size of the reserved memory 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::cout << "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 is clearly 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 sequence of allocation 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.   

 

 

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

 

Tags: memory

Add comment


My Newest E-Book

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 222

All 257842

Currently are 231 guests and no members online