10

I decided to overload the new, new[],... operators in my classes so I can log the file and line at which they were called so I can easier track memory allocations/leaks.

Now the problems is in my stack and array classes (and other template container classes which allocate memory):

If I use them with one of my classes which has the new,new[],... operators overloaded it works fine.

But if I use it with the standard c++ data types (int,float,...) I can't allocate them, since no overloaded new operator matches the arguments of the new(__ LINE __ , __ FILE __) operator (or others like placement new).

Example of stack code:

// placement new
T* t=new(__ LINE __ , __ FILE__)(&m_data[i])T;

So I'm out of good ideas on how to make this work. If I replace new(__ LINE __ ,__ FILE __) with new I loose memory logging ability. One solution is to make a separated stack for standard data types in which the default new is used.

Is there any way to detect at compile time if a template parameter is a struct, class or a built in c++ type?

How do you handle stuff like this? What do you suggest? Any comments on this design (good,bad) are obviously welcome (just don't post stuff like "don't reinvent the wheel with your own containers ").

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
n3XusSLO
  • 227
  • 4
  • 11
  • 1
    Have you overloaded `operator new(__LINE__, __FILE__)` globally and tried to use with `int`, `float` etc. ? – iammilind Jul 22 '11 at 10:11
  • One option would be to replace global `operator new()` and friends. – sharptooth Jul 22 '11 at 10:12
  • 5
    If it's memory leak debugging you are after, I don't see why you're doing the work of something that already exists for this: Valgrind? – Nim Jul 22 '11 at 10:18
  • Maybe [this](http://stackoverflow.com/questions/2835416/) helps? – fredoverflow Jul 22 '11 at 10:19
  • 1
    Additionally, one might ask why you need to allocate primitive data types dynamically... perhaps the design could be reworked to require less dynamic storage? – Kerrek SB Jul 22 '11 at 10:38
  • @iammilind: do you mean like `#define new new(__LINE__,__FILE__)`? This doesn't work. @sharptooth: I could do that, but there is a chance that this would cause a big mess: some classes I use (third party libs) use their own overloaded new operator, besides I also need to overload the placement new, but as I read you can't do this using your suggestion (correct me if I'm wrong). @Nim: I could, but I'd like to make everything from scratch, since I'm interested in low level memory management concepts (just for the pure "fun" of it 9+ experimentation of how this will turn out). (continued below:) – n3XusSLO Jul 22 '11 at 12:30
  • If this would be a commercial product I'd obviously go for some more thorough tools. @ FredOverflow: you don't really overload the new operators the way I want to, so they report the line at which the new operator was called. @Kerrek SB: you serious? Examples: loading 3d models from file: load everything into a char array first, then as you go parse the loaded file in some other thread etc... – n3XusSLO Jul 22 '11 at 12:37
  • Because you have `std::vector`, there's almost no reason why there should be a single `new[]` in your code. - It is possible to eliminate even the faintest chance of a leak in 99.9% of cases (containers, smart pointers), while also having way simpler code. – visitor Aug 17 '11 at 11:38

3 Answers3

2

Please note that your current solution requires adding the logging code to every new(line, file) overload you have. Also, you cannot easily switch it off in release builds unless you surround each of your logging calls within #ifndef DEBUG ... #endif.

Here's one way of achieving what you wnat: Instead of overloading the new operator for each of your classes, consider overloading the global new operator using the placement syntax; that way you avoid interfering with the 'normal' new operator. Then you can #define new and delete macros for convenience and, most importantly, you can have control over when your memory-tracking new/delete is applied and when the standard version is used.

#ifdef ENABLE_CUSTOM_ALLOC

// Custom new operator. Do your memory logging here.
void* operator new (size_t size, char* file, unsigned int line)
{
    void* x = malloc(size);
    cout << "Allocated " << size << " byte(s) at address " << x 
        << " in " << file << ":" << line << endl;
    return x;  
}

// You must override the default delete operator to detect all deallocations
void operator delete (void* p)
{
   free(p);
   cout << "Freed memory at address " << p << endl;
}

// You also should provide an overload with the same arguments as your
// placement new. This would be called in case the constructor of the 
// created object would throw.
void operator delete (void* p, char* file, unsigned int line)
{
   free(p);
   cout << "Freed memory at address " << p << endl;
}

#define new new(__FILE__, __LINE__)

#endif


// A test class with constructors and destructor
class X
{
public: 
    X() { cout << "X::ctor()" << endl; }
    X(int x) { cout << "X::ctor(" << x << ")" << endl; }
    ~X() { cout << "X::dtor()" << endl; }
};


int main (int argc, char* argv[])
{
    X* x3 = new X();
    X* x4 = new X(20);
    delete x3;
    delete x4;
}

you should see something like:

Allocated 1 byte(s) at address 00345008 in Alloc.cpp:58
X::ctor()
Allocated 1 byte(s) at address 003450B0 in Alloc.cpp:59
X::ctor(20)
X::dtor()
Freed memory at address 00345008
X::dtor()
Freed memory at address 003450B0

Try substituting X for int and you'll see it works too. You can extend this to the array and placement new as well but i'd rather not make the post longer than it is.

Few last pointers at the end:
- MSVC has this functionality, see here
- There is a toturial about doing memory tracking this way here under the 'Tracing Memory Leaks' section

Martin Gunia
  • 1,091
  • 8
  • 12
0
struct Int {
    int i;
    Int (int _i) : i(_i) {}
    operator int () const {return i;}
};

#define __LINE_NUMBER__ Int(__LINE__)

Use this macro instead of the standard line number macro and overload resolution will distinguish Int line numbers from int other numbers.

I can't imagine how this will work out in full. Are you going to use it like int * x = NEW(int,123); or something like that?

I agree with the commenters, by the way -- you probably don't have to go down this road. Overloading new is something of a black art and should normally be avoided.

spraff
  • 32,570
  • 22
  • 121
  • 229
  • I don't think you understand what I want: there is no problem with the __LINE__ macro. Or did you want to tell me something else? – n3XusSLO Jul 22 '11 at 12:39
  • The problem is that `__LINE__` yields an int which interferes with overload resolution, right? Here I wrap it in a class so that the overloads are different. – spraff Jul 22 '11 at 12:46
0

Is there any way to detect at compile time if a template parameter is a struct, class or a built in c++ type?

You can use boos::type_traits and boost::mpl for it.

Example:

#include <boost/type_traits.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/logical.hpp>

template <class T>
typename boost::enable_if<boost::is_class<T>, T>::type
foo(){cout << "is class " << endl;};

template <class T>
typename boost::enable_if<boost::mpl::not_<boost::is_class<T> >, T>::type
foo(){cout << "is not class "<< endl;};

Types list - http://www.boost.org/doc/libs/1_47_0/libs/type_traits/doc/html/index.html

Or you can use boost::mpl::set for your set of types

kovalevfm
  • 124
  • 2
  • I'm still trying to wrap my head around this thing: in a template class, could I make the compiler choose to use the default new or the overloaded new at compile time based on the template parameter type (so if it is a struct or class it would choose overloaded new, but if it would be anything else, it would choose the default new?) – n3XusSLO Jul 22 '11 at 12:49