Verifying C++ Destructor Cleanup for regular pointers

Started by
10 comments, last by swiftcoder 6 years, 10 months ago

I am working with an older game engine code base that uses regular * pointers (not smart pointers).

I want to verify that my destructor for a GameLevel objects is cleaning up all the memory that it allocated.

My first idea was:

measure memory used by process
new up, then delete a GameLevel object
measure memory again and make sure its the same

I don't know if functions exist to accurately measure the process memory. I'm using the SDL2 framework.

I could refactor the code and try to find every single place its using a * pointer but that could be quite a task...

Ideas and advice would be appreciated.

Advertisement
This is a non-starter for various reasons. The way the heaps works, the process doesn't release the memory to the OS when a pointer is deleted. That's probably the most obvious obstacle. You also have the issue that other memory might have been allocated during the lifetime of the object. Either way: No.
There are numerous "leak checkers" available in various flavors for different platforms. I'd suggest starting with that.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Is it an immediate problem? i.e. do you know that there are currently memory leaks?

If so, identify where the issue is using the tools @[member='ApochPiQ'] mentioned, and refactor to smart pointers.

Otherwise, just make a note to clean up the code as you work on it in future.

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

There is no compelling need of using smart pointers at all. Sure they help programmers but they also let programmers intend to go on the "allocate and forget about" track that may work in something like Java or C# but should not be approached in C++. We have the possibilities to manage what goes where in memory, on the stack and the heap so we are responsible for keeping an eye on it.

To simply keep track on your new/delete calls try either replacement new model with some kind of counter behind it or go directly to an allocater based approach like described here http://bitsquid.blogspot.de/2010/09/custom-memory-allocation-in-c.html

I'm using a similar system for over 2 years now and never had any problems with memory leaks while responsible allocators would have raised an error so I was able to fix it directly

i really like valgrind's memcheck. also there are libraries for c++ that do memory accounting for you. malloc-libraries, like tcmalloc which comes with google's perfomance tools: http://goog-perftools.sourceforge.net/

Whenever I'm writing old-school C-style memory management code in C++ these days (i.e. raw pointers, not 100% RAII, no "smart" anything), I now use a template like this that asserts when you forget to clean up properly. It also greatly helps to annotate which raw pointers are users of an object, and which raw pointer is the actual owner of an object:


template<class T> struct Owner 
{
	~Owner() { ASSERTMSG(p == 0, "Leak detected"); }
	void Release() { if(p) p->Release(); p = 0; }
	T* Relinquish() { T* copy = p; p = 0; return copy; }
	explicit Owner(T* data=0) : p(data) {}
	Owner(const Owner& o) : p(o.p) {o.p=0;}
	Owner<T>& operator=(const Owner& o) { ASSERTMSG(p == 0, "Leak detected"); p = o.p; o.p=0; return *this; }
	Owner<T>& operator=(T* data) { ASSERTMSG(p == 0, "Leak detected"); p = data; return *this; }
	operator Ptr<T>() const { return p; }
	T* operator->() const { ASSERT(p, "null dereference"); return  p; }
	T& operator *() const { ASSERT(p, "null dereference"); return *p; }
	operator bool() const { return !!p; }
	T* Raw() const { return p; }
private:
	mutable T* p;
};

//e.g.
struct Foo { 
  //int* m_A;//bad
  //int* m_B;//bad
  Owner<int> m_A;
  Ptr<int> m_B;
  Foo() { m_A = new int; }
  ~Foo() { delete m_A.relinquish(); }// if you forget to write this part, you'll get an assertion failure!
};

And I use this one for non-owning pointers to ensure that uninitialized pointer bugs don't ever happen:


template<class T> struct Ptr
{
	Ptr(T* data=0) : p(data) {}
	operator T*() const { return p; }
	T* operator->() const { ASSERTMSG(p, "null dereference"); return  p; }
	T& operator *() const { ASSERTMSG(p, "null dereference"); return *p; }
	operator bool() const { return !!p; }
	T* Raw() const { return p; }
private:
	T* p;
};

In optimized builds there's basically no overhead compared to actually using raw pointers, but in development builds they catch all sorts of bugs.
I've had similar templates forced onto me during several game projects in the past and I really didn't like them at the time... however, 5 years of fixing stupid bugs later and now I'm converted and will recommend this practice to others :D

I haven't looked into it fully, but I believe that the C++ Core Guidelines project supplies similar templates to mine (except probably much more robust!).

They're on the way, yeah. I think the current advice (Sean would probably know a lot more about it) is to use unique_ptr for ownership and raw pointers otherwise, with the idea of assuming that raw pointers are non-owning, but that can easily go wrong if someone drops the ball. I'm hoping we see a lot of the new stuff soon.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

If everything derives from a common base-class then you can override new,delete,new[],delete[] and do book-keeping there.

- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

Kinda in response to Shaarigan, I have to depart on that assessment of smart pointers. If I'm not mistaken the whole point of their existence is to acknowledge that things can, and do happen in a large enough code base (exceptions, most notably) that prevent the otherwise predictable flow control of the application reaching a method, or destructor that would otherwise perform the raw memory management.

In response to OP, If the codebase is small enough, one option is just to 'Caveman' it. That is place a breakpoint at the point the application receives a QUIT message of some sort, and just step through the object deallocations. Obviously this can be very error prone, so I wouldn't recommend for any codebase of substantial size.

This topic is closed to new replies.

Advertisement