Overloading Operator new and delete 2

Contents[Show]

I overloaded in the last post operator new and delete. Therefore, it was possible to find memory leaks and get a first hint of the bad guys. My solution had two not so nice properties. With this post, I will overcome them.

What were the not so nice properties of my last post? At first, I get only a hint which memory was lost; at second, I had to prepare the whole bookkeeping of the memory management at compile time. Who want to know the details of these shortcomings should read the last post. But now the ugliness will go.

Who is the  bad guy?

Special tasks ask for special forces. So I have to use a small macro for debugging purposes.

I want to stress this point. I'm not a friend of macros.

Let's have a look at the macro. #define new new(__FILE__, __LINE__)

The macro causes that each new call will be mapped to the overloaded new call. This overloaded new call gets in addition the name of the file and the line number, respectively. That's exactly the information I need.

But what will happen, if I use the macro in line 6?

 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
// overloadOperatorNewAndDelete2.cpp

#include "myNew4.hpp"
// #include "myNew5.hpp"

#define new new(__FILE__, __LINE__)

#include <iostream>
#include <new>
#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;
    
    dummyFunction();
    
    getInfo();
    
}

 

The preprocessor substitutes all new calls. That shows exactly the modified main function.

 

 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
class MyClass{
  float* p= new("overloadNewAndDelete.cpp", 14) float[100];
};

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

int main(){

    int* myInt= new("overloadNewAndDelete.cpp", 24) int(1998);
    double* myDouble= new("overloadNewAndDelete.cpp", 25) double(3.14);
    double* myDoubleArray= new("overloadNewAndDelete.cpp", 26) double[2]{1.1,1.2};

    MyClass* myClass= new("overloadNewAndDelete.cpp", 28) MyClass;
    MyClass2* myClass2= new("overloadNewAndDelete.cpp", 29) MyClass2;

    delete myDouble;
    delete [] myDoubleArray;
    delete myClass;
    delete myClass2;

    dummyFunction();

    getInfo();

}

 

The lines 2 and 12 shows that the preprocessor substitutes the constants __FILE__ and __LINE__ in the macro. But how does the magic work? The header myNew4.hpp solves the riddle.

 

 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// myNew4.hpp

#ifndef MY_NEW4
#define MY_NEW4

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <new>
#include <array>

int const MY_SIZE= 10;

int counter= 0;

std::array<void* ,MY_SIZE> myAlloc{nullptr,};

void* newImpl(std::size_t sz,char const* file, int line){
    void* ptr= std::malloc(sz);
    std::cout << file << ": " << line << " " <<  ptr << std::endl;
    myAlloc.at(counter++)= ptr;
    return ptr;
}

void* operator new(std::size_t sz,char const* file, int line){  
    return newImpl(sz,file,line);
}

void* operator new [](std::size_t sz,char const* file, int line){  
    return newImpl(sz,file,line);
}

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);
}

#define new new(__FILE__, __LINE__)

void dummyFunction(){
    int* dummy= new int;
}

void getInfo(){
    
    std::cout << std::endl;
     
    std::cout << "Allocation: " << std::endl;
    for (auto i: myAlloc){
        if (i != nullptr ) std::cout << " " << i << std::endl;
    }
    
    std::cout << std::endl;
    
}

#endif // MY_NEW4

 

I implement in the line 25 and 29 the special operators new and new[] that delegates their functionality to the helper function newImpl (line 18 - 23). The function does two important jobs. At first, it displays to each new call the name of the source file and the line number (line 20); at second, it keeps in the static array myAlloc track of each used memory address (line 21). This fits to the behaviour of the overloaded operator delete that sets all memory addresses to the null pointer nullptr (line 35). The memory addresses stands for the deallocated memory areas. At the end the function getInfo displays the memory addresses that were not deallocated. You can directly see them together with the file name and line number.

Of course, I can directly apply the macro in the file myNew4.hpp. That was a lot of theory. What's the output of the program?

overloadNewAndDelete 

The memory areas to the memory addresses 0x8c3010, 0x8c3090, and 0x8c3230 were not deallocated. The bad guys are the new calls in the line 24 and line 14 (overloadNewAndDelete.hpp) and the new call in the line 42 (myNew4.hpp).

Impressed? I assume, yes. But the presented technique has two drawbacks. One minor and one major.

  1. I have to overload the simple operator new and the operator new [] for arrays. This, because the overloaded operator new is not a fallback for the three remaining operators new.
  2. I can not use the special operator new that returns in the error case a null pointer. Because, it will be explicitly called by the operator new with the argument std::nothrow: int* myInt= new (std::nothrow) int(1998);

Now, I have to solve the first issue. I want to use for the array myAlloc a data structure that manages its memory at run time. Therefore, it is not necessary any more to eagerly allocate the memory at compile time .

All at run time

What was the reason that I can't allocate memory in the operator new? The operator new was globally overloaded. Therefore, a call of new would end in a never ending recursion. That will exactly happen, if I use a container like std::vector that dynamically allocates its memory.

This restriction holds not any more because I didn't overload the global operator new that is a fallback to the three remaining new operators. Thanks to the macro, my own variant of operator new is used. Therefore, I can use std::vector in my operator new.

Exactly that can you see in my program.

 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
45
46
47
48
49
50
51
52
53
54
55
56
// myNew5.hpp

#ifndef MY_NEW5
#define MY_NEW5

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <new>
#include <string>
#include <vector>

std::vector<void*> myAlloc;

void* newImpl(std::size_t sz,char const* file, int line){
    static int counter{};
    void* ptr= std::malloc(sz);
    std::cout << file << ": " << line << " " <<  ptr << std::endl;
    myAlloc.push_back(ptr);
    return ptr;
}

void* operator new(std::size_t sz,char const* file, int line){  
    return newImpl(sz,file,line);
}

void* operator new [](std::size_t sz,char const* file, int line){  
    return newImpl(sz,file,line);
}

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);
}

#define new new(__FILE__, __LINE__)

void dummyFunction(){
    int* dummy= new int;
}

void getInfo(){
    
    std::cout << std::endl;
     
    std::cout << "Allocation: " << std::endl;
    for (auto i: myAlloc){
        if (i != nullptr ) std::cout << " " << i << std::endl;
    }
    
    std::cout << std::endl;
    
}

#endif // MY_NEW5

 

I use in line 13, 19, and 33 std::vector.

What's next?

I the next post I will have a closer look at std::allocator. In particular, I'm interested how memory requests can be mapped to special memory areas.

 

 

 

 

 

 

 

 

 

 

 

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 169

All 277623

Currently are 148 guests and no members online