Skip to main content
GameDev.net gamedev.net
🔒 Locked

[C++] Heap error/memory freeing

Started by claesson92 Jan 19, 2010 at 12:09 PM 21 replies 4.8k views
Original Post
claesson92
claesson92
I'm having a problem which seems to only occour in debug mode. I get an error (at runtime) about some heap error, and also says there is a bug in the program. I know/think that it has to do with that i'm either using freed memory, or freeing something twice. The code is based on the enguinity guides. When i get the error i get some info about a triggered breakpoint in xutility. The error is caused by MemoryObject::collectRemaining(). Or more specifically, the line "delete o" in that method. I seem to delete/free something which already is freed. Or, something is trying to free it afterwards. I'm not able to fix this problem though. The debugger tells me the problem happends in free.c at this line:
retval = HeapFree(_crtheap, 0, pBlock);

This is not anything i've written. It's related to delete. In the following code, much has been stripped away. There are a few calls to functions such as getLength(), m_getAllocatedElements() and so, but i think their names explains what they does. Here are the (stripped) relevant code: main.cpp

#include "CGDK.hpp" //This includes Collection.hpp, MemoryObject.hpp, MemoryPointer.hpp and some standard C++ headers

using namespace std;
using namespace CGDK::core;

int main(int argc, char *argv[])
{
	MemoryPointer<Vector<int>> m = new Vector<int>(25);

	MemoryObject::collectRemaining();

	return 0;
}


MemoryPointer.hpp - Full code

#include "_stdlibrary.hpp" //Includes some standard c++ headers and my own classes

//Class definition...

template<typename T>
CGDK::core::MemoryPointer<T>::MemoryPointer()
{
	m_object = 0;
}

template<typename T>
CGDK::core::MemoryPointer<T>::MemoryPointer(T *obj)
{
	m_object = 0;
	*this = obj;
}
template<typename T>
CGDK::core::MemoryPointer<T>::~MemoryPointer()
{
	if(m_object)
	{
		m_object->release();
	}
}

template<typename T>
void CGDK::core::MemoryPointer<T>::operator=(T *obj)
{
	//Decrement the reference counter if the is an object
	if(m_object)
	{
		m_object->release();
	}

	m_object = obj;

	//Increment the reference counter...
	if(m_object)
	{
		m_object->addReference();
	}
}

template<typename T>
T* CGDK::core::MemoryPointer<T>::operator->() const
{
	CGDK_ASSERT(m_object != 0, "Tried to -> on a NULL smart pointer");

	return m_object;
}




Vector.hpp - Full code

//Some includes

template<typename T>
class Vector : public CGDK::core::Collection<T>
{
    //Some code
};

template<typename T>
void CGDK::core::Vector<T>::m_initVector(T initvalue)
{
	m_maxSize = 0;

	//Set the initial value of new elements
	setInitialValue(initvalue);
}

template<typename T>
CGDK::core::Vector<T>::Vector() : CGDK::core::Collection<T>::Collection(10, 5)
{
	m_initVector();
}

template<typename T>
CGDK::core::Vector<T>::Vector(unsigned int size, T initvalue) : CGDK::core::Collection<T>::Collection(size, 5)
{
	m_initVector(initvalue);
}

template<typename T>
CGDK::core::Vector<T>::~Vector()
{

}

template<typename T>
void CGDK::core::Vector<T>::setMaximumSize(unsigned int max)
{
	m_maxSize = max;
}

template<typename T>
unsigned int CGDK::core::Vector<T>::getMaximumSize()
{
	return m_maxSize;
}


Collection.hpp - Full code

//Includes

template<typename T>
class Collection<T> : public CGDK::core::MemoryObject
{
    //Some code...
};

template<typename T>
void CGDK::core::Collection<T>::m_realloc(unsigned long elements)
{
	unsigned long _oldsize = m_allocatedElements;

	try
	{
		m_memPointer = new T[elements];
		m_allocatedElements = elements;
	}
	catch(std::bad_alloc e)
	{
		CGDK_THROW("Unable to (re)allocate memory");
	}
	
	for(unsigned long i = _oldsize; i < m_allocatedElements; i++)
	{
		set(i, m_initValue);
	}
}

template<typename T>
void CGDK::core::Collection<T>::m_pushBack(T val)
{
	if(m_getFreeElements() == 0)
	{
		//We need to allocate more memory
		m_grow();
	}

	m_memPointer[m_usedElements] = val;
	m_usedElements++;
}

template<typename T>
void CGDK::core::Collection<T>::m_init(unsigned long elements, unsigned int growth)
{
	m_initValue = static_cast<T>(0);
	m_growth = growth;
	m_allocatedElements = elements;
	m_usedElements = 0;

	try
	{
		m_memPointer = new T[elements];
	}
	catch(std::bad_alloc e)
	{
		CGDK_THROW("Unable to allocate memory");
	}
}

template<typename T>
CGDK::core::Collection<T>::Collection()
{
	m_init(5, 5);
}

template<typename T>
CGDK::core::Collection<T>::Collection(unsigned long elements, unsigned int growth)
{
	m_init(elements, growth);
}

template<typename T>
CGDK::core::Collection<T>::~Collection()
{
	delete[] m_memPointer;
}






MemoryObject.cpp - This is where it crashes - Full code

#include "MemoryObject.hpp" //Contains the class definition and some includes
#include "Error.hpp"

std::list<CGDK::core::MemoryObject*> CGDK::core::MemoryObject::m_liveObjects;
std::list<CGDK::core::MemoryObject*> CGDK::core::MemoryObject::m_deadObjects;

void CGDK::core::MemoryObject::addReference()
{
	++m_referenceCount;
}

void CGDK::core::MemoryObject::release()
{
	--m_referenceCount;

	if(m_referenceCount <= 0)
	{
		m_liveObjects.remove(this);
		m_deadObjects.push_back(this);
	}
}

CGDK::core::MemoryObject::MemoryObject()
{
	m_liveObjects.push_back(this);
	//m_listPosition = static_cast<unsigned long>(m_liveObjects.size()) - 1;

	//Set the initial reference count to 0
	m_referenceCount = 0;
}

CGDK::core::MemoryObject::~MemoryObject()
{
}

void CGDK::core::MemoryObject::collectGarbage()
{
	for(std::list<MemoryObject*>::iterator it = m_deadObjects.begin(); it != m_deadObjects.end(); it++)
	{
		MemoryObject *o = (*it);
		delete(o);
		it++;
	}

	m_deadObjects.clear();
}

void CGDK::core::MemoryObject::collectRemaining()
{
	collectGarbage();

	for(std::list<MemoryObject*>::iterator it = m_liveObjects.begin(); it != m_liveObjects.end(); it++)
	{
		CGDK::core::MemoryObject *o=(*it);
		delete o; ///////// <-- This is where it crashes
	}

	m_liveObjects.clear();
}




Sorry for all that code, but i didn't want to miss anything, if the error were caused by something i didn't expect. [Edited by - claesson92 on January 20, 2010 2:14:36 AM]
zyrolasting
zyrolasting
Hello!

Posting obfuscated code will rarely attract much help. You are missing a couple of relevant headers in your posted source, but we definitely do not want to drown in unstable, undocumented code we did not write. Also, it seems you have a lead anyway.

Quote:
The error is caused by MemoryObject::collectRemaining(). Or more specifically, the delete *o in that method. I seem to delete/free something which already is freed. Or, something is trying to free it afterwards.


Have you looked into that theory? You've certainly got enough traces going on to check on that. If you have, try to slim this down before asking for help. Leave only the code directly related to working with dynamic memory and leave all the rest out. It looks like you are trying to make a managed heap, correct?
MaulingMonkey
MaulingMonkey
Please use your debugger and tell us the exact line it's crashing on in the future.

Eyeballing over it, I only see this problem:
void CGDK::core::MemoryObject::collectGarbage(){	for(std::list<MemoryObject*>::iterator it = m_deadObjects.begin(); it != m_deadObjects.end(); /*[1]*/ it++)	{		MemoryObject *o = (*it);		delete(o);		it++; // <-- moves to the next element -- in the example, it moves to m_deadObjects.end().		// YOU ALSO DO it++ IN YOUR LOOP!  See [1].		// the loop will (try to) move it to past .end() which isn't legal.		// If it doesn't explode immediately, it probably won't == .end() and the loop may go on 'forever'.		// I'm guessing it crashes in this loop, and would've spotted it fairly instantly if I knew that it did.	}	m_deadObjects.clear();}
claesson92
claesson92
I'm sorry for that code. And that i forgot the code that handles the dynamic memory. I have stripped away some code, and added the missing class. Take a look at the first post to see the code.

Quote:
Original post by zyrolasting
It looks like you are trying to make a managed heap, correct?

This is something like a garbage collector. When there are no refence to an object, delete it to free the memory.

Quote:
Original post by MaulingMonkey
Eyeballing over it, I only see this problem:
*** Source Snippet Removed ***

Thank you, i didn't see that. This problem is not related to that though, as in this example there wont be any elements in m_deadObjects. But that saved me some trouble in the future.

[Edited by - claesson92 on January 19, 2010 2:02:13 PM]
claesson92
claesson92
Sorry for the double post, but i have still not been capable of solving the problem. Anyone has any idea of what might be causing the crash? I think it has something to do with that i'm either freeing the same memory multiple times, or that i'm using/referencing freed memory. But i can't find where that would be.
Windryder
Windryder
I only had time to look at your code briefly, but I noticed that MemoryPointer lacks a copy constructor even though it has an assignment operator and a destructor. This is a violation of the C++ Rule of Three, which will cause trouble later on even if it's not causing the problem you are experiencing right now. I'd take a look at that, and especially how the lack of a proper copy constructor affects reference counting.

On a side note, I noticed that the MemoryPointer class lacks a dereferencing operator and only provides a const version of operator->, which means that it's entirely possible to call non-const functions on the object pointed to. Have a look at what the C++ FAQ has to say about it.
claesson92
claesson92
Quote:
Original post by Windryder
I only had time to look at your code briefly, but I noticed that MemoryPointer lacks a copy constructor even though it has an assignment operator and a destructor. This is a violation of the C++ Rule of Three, which will cause trouble later on even if it's not causing the problem you are experiencing right now. I'd take a look at that, and especially how the lack of a proper copy constructor affects reference counting.

On a side note, I noticed that the MemoryPointer class lacks a dereferencing operator and only provides a const version of operator->, which means that it's entirely possible to call non-const functions on the object pointed to. Have a look at what the C++ FAQ has to say about it.


It has a copy constructor and a dereference operator. I stripped it out for this post as i didnt think it was relevant for this problem.
Ezbez
Ezbez
Quote:
Original post by claesson92
The error is caused by MemoryObject::collectRemaining(). Or more specifically, the line "delete o" in that method. I seem to delete/free something which already is freed. Or, something is trying to free it afterwards. I'm not able to fix this problem though.


I want to make sure that you understand something and are not just mixing terminology:

You cannot free() what you new, and you cannot delete what you malloc()'d. They are not interchangeable. delete must be used on anything new'd and free() must be used on anything malloc()'d. But hopefully you were just using "free" as a more general form of releasing resources! I don't see any free or malloc in your code, so it looks like you understand.
claesson92
claesson92
Quote:
Original post by the_edd
Do your Vector<> and MemoryPointer<> objects work correctly independently?


The Vector class works fine. The problem is in the MemoryObject class.
I wrote a very simple class and tested it with the same code as Vector. And the program crashes and the debugger shows me something in xutility caused the crash. Commenting out delete o; in collectRemaining() stops it from crashing.

main.cpp
#include "CGDK.hpp"using namespace std;using namespace CGDK::core;class Test : public MemoryObject{private:	int x;public:	Test();	CGDK_GETMEMORYSIZE_METHOD; //If anyone wonders, this is a inline unsiged long that returns sizeof(*this). It is not used for anything. Yet.};Test::Test(){	x = 0;}int main(int argc, char *argv[]){	MemoryPointer<Test> t = new Test();	MemoryObject::collectRemaining(); //Crashes here. At delete o; in that method	return 0;}


Quote:
Original post by Ezbez
I want to make sure that you understand something and are not just mixing terminology:

You cannot free() what you new, and you cannot delete what you malloc()'d. They are not interchangeable. delete must be used on anything new'd and free() must be used on anything malloc()'d. But hopefully you were just using "free" as a more general form of releasing resources! I don't see any free or malloc in your code, so it looks like you understand.


I do understand that, i'm not very careful with what i'm writing ;)
Evil Steve
Evil Steve
That error is caused by double-deleting a pointer, or by causing heap corruption.

Can you create a minimal example program and upload the full source (or shove it in a single .cpp and .hpp file and paste it here in source tags)?
Zipster
Zipster
Have you tried good 'ol fashioned "printf debugging"? Basically, whenever you modify the m_liveObjects and m_deadObjects lists, print out the address being added/removed. Also print the address of everything being deleted. With such a small test case causing the crash you'll be able to tell immediately if you're double-deleting a pointer, or perhaps deleting an invalid pointer.
DragonMasterHawk
DragonMasterHawk
Two things.

1. I see absolutely no reason to subtract 1 from all your sizes. Unless it's some hidden implementation detail, size() returns the exact number of elements in a container. This is definitely not your current problem, but it's something to look into.

2. Is MemoryObject's destructor virtual?
claesson92
claesson92
Quote:
Original post by DragonMasterHawk
Two things.

1. I see absolutely no reason to subtract 1 from all your sizes. Unless it's some hidden implementation detail, size() returns the exact number of elements in a container. This is definitely not your current problem, but it's something to look into.

2. Is MemoryObject's destructor virtual?


Yes, the destructor is virtual. And i'll take a look at the size thing.

I will try to see if it deletes something twice or deleting an invalid pointer or such. I'll post again when i've got the results.
claesson92
claesson92
I am deleting the correct pointer. At least it inserts the same address to m_liveObjects as it deletes. And it is on "delete o;" it crashes.

I've tested, and o is not NULL.

[Edited by - claesson92 on January 21, 2010 11:52:52 AM]
claesson92
claesson92
I've tested a few ways, NULL test, sizeof and couting the adresses. But i can't find out what is causing the crash. I can't find that o is anything else than it should. Or any reason why it could not be deleted.

Am i doing something wrong, or what is wrong? Might anything that happends before the delete cause it to malfunction when i'm deleting the pointer?

Here is a zip file containing all the code (and a solution file for Visual Studio): DeleteProblem.zip

I tried to put everything in a header, and a source file to give you shorter code. Now though.. it does not crash. But i have no idea why.

m.hpp
#include <string>#include <list>/////////////////////////////////// MemoryPointer.hpp/////////////////////////////////template<typename T>class MemoryPointer{protected:	T* m_object;public:	MemoryPointer();	MemoryPointer(T* obj);	MemoryPointer(const MemoryPointer<T> &pointer);		~MemoryPointer();	inline void operator=(T* obj);	inline void operator=(const MemoryPointer<T> &pointer);	//Reference access	inline T& operator *() const;	//Pointer access	inline T* operator ->() const;};template<typename T>MemoryPointer<T>::MemoryPointer(){	m_object = 0;}template<typename T>MemoryPointer<T>::MemoryPointer(T *obj){	m_object = 0;	*this = obj;}template<typename T>MemoryPointer<T>::MemoryPointer(const MemoryPointer<T> &pointer){	m_object = 0;	*this = pointer;}template<typename T>MemoryPointer<T>::~MemoryPointer(){	if(m_object)	{		m_object->release();	}}template<typename T>void MemoryPointer<T>::operator=(T *obj){	//Decrement the reference counter if the is an object	if(m_object)	{		m_object->release();	}	m_object = obj;	//Increment the reference counter...	if(m_object)	{		m_object->addReference();	}}template<typename T>void MemoryPointer<T>::operator=(const MemoryPointer<T> &pointer){	//Decrement the reference counter if the is an object	if(m_object)	{		m_object->release();	}	m_object = pointer.m_object;	//Increment the reference counter...	if(m_object)	{		m_object->addReference();	}}template<typename T>T& MemoryPointer<T>::operator*() const{	return *m_object;}template<typename T>T* MemoryPointer<T>::operator->() const{	return m_object;}////////////////////////////////// MemoryObject.hpp  -  Method bodies in cpp file////////////////////////////////class MemoryObject{private:	static std::list<MemoryObject*> m_liveObjects;	static std::list<MemoryObject*> m_deadObjects;	long m_referenceCount;protected:	MemoryObject();	virtual ~MemoryObject();public:	inline void addReference()	{		++m_referenceCount;	}	inline void release()	{		--m_referenceCount;		if(m_referenceCount <= 0)		{			m_liveObjects.remove(this);			m_deadObjects.push_back(this);		}	}	static void collectGarbage();	static void collectRemaining();	static unsigned long getObjectCount();	virtual unsigned long getMemorySize()=0;};




m.cpp
#include "m.hpp"class TestClass : public MemoryObject{public:	int x;	TestClass(int num);	inline unsigned long getMemorySize()	{		return sizeof(*this);	}};TestClass::TestClass(int num){	x = num;}int main(){	MemoryPointer<TestClass> ptr = new TestClass(5);		MemoryObject::collectRemaining();		return 0;}////////////////////////////// MemoryObject.cpp///////////////////////////std::list<MemoryObject*> MemoryObject::m_liveObjects;std::list<MemoryObject*> MemoryObject::m_deadObjects;MemoryObject::MemoryObject(){	m_liveObjects.push_back(this);	//m_listPosition = static_cast<unsigned long>(m_liveObjects.size()) - 1;	//Set the initial reference count to 0	m_referenceCount = 0;}MemoryObject::~MemoryObject(){}void MemoryObject::collectGarbage(){	for(std::list<MemoryObject*>::iterator it = m_deadObjects.begin(); it != m_deadObjects.end(); it++)	{		MemoryObject *o = (*it);		delete o;	}	m_deadObjects.clear();}void ::MemoryObject::collectRemaining(){	collectGarbage();	for(std::list<MemoryObject*>::iterator it = m_liveObjects.begin(); it != m_liveObjects.end(); it++)	{		::MemoryObject *o=(*it);		delete o; //CRASH	}	m_liveObjects.clear();}unsigned long ::MemoryObject::getObjectCount(){	return static_cast<unsigned long>(m_liveObjects.size()) - 1;}


[Edited by - claesson92 on January 21, 2010 12:56:26 PM]
the_edd
the_edd
Quote:
Original post by claesson92
I am deleting the correct pointer. At least it inserts the same address to m_liveObjects as it deletes.


Have you checked that you aren't deleting it twice, as Evil Steve suggested?

Also, though this is obviously lots of fun and everything, might I suggest looking at boost::shared_ptr and/or boost::intrusive_ptr?
claesson92
claesson92
Quote:
Original post by the_edd
Quote:
Original post by claesson92
I am deleting the correct pointer. At least it inserts the same address to m_liveObjects as it deletes.


Have you checked that you aren't deleting it twice, as Evil Steve suggested?

Also, though this is obviously lots of fun and everything, might I suggest looking at boost::shared_ptr and/or boost::intrusive_ptr?


I have not been able to find wehere i would delete it twice, but i might very well have missed something.

I would rather make those classes (MemoryPointer/MemoryObject) work, but i will take a look at boost.

DragonMasterHawk
DragonMasterHawk
Found it.

You collect remaining, which deletes it and removes it from the live list, but you don't NULL out the m_object member variable of the MemoryObject. Thus, when the program ends, MemoryPointer's destructor sees that m_object isn't NULL (and is, in fact, garbage), and tries to release it. Release sees that m_referenceCount is <= 0 (and is, in fact, garbage), and tries to remove from the liveList and push_back onto the deadList. remove does nothing, so it's fine. push_back allocates memory for the first time since you modified m_referenceCount in release(), which is already freed memory, so it's the first time it can break.

Now, yours broke elsewhere. Heap corruption is a wild and random thing that can depend heavily on implementation-define initialization order and other things. Try fixing this problem and see if the other goes away.
the_edd
the_edd
Quote:
Original post by claesson92
I have not been able to find wehere i would delete it twice, but i might very well have missed something.


!

Don't examine it by eye and brain! That hasn't got you anywhere so far. In fact it hasn't got any of us anywhere so far, with the exception of the eagle-eyed DragonMasterHawk.

There's nothing wrong with sticking in some printfs/couts to show the pointers returned-from/passed-to new/delete.

In general you seem to be reluctant to get down and dirty and find out what's going wrong for yourself. You should try to work on that. You'll come across harder problems than this in future, for sure.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.