Memory Management – Part 1

I was originally going to post a piece on iOS memory management, but it turned into a much larger piece on memory manangement in general. So, here’s the first part – other parts will follow later…

Understanding what memory management is and why you need to do it.

There are two main points that form the basis of all of this:

Every device has a limited amount of memory available, which means it can only store a limited amount of information. Imagine the memory as a pile of empty boxes, ready to be filled with stuff.

Every time you create an object, you are grabbing one of these empty boxes and using it to store your object in. That object will now hapilly live inside the box for the rest of its life.

Note the term “for the rest of its life” – this is the important bit. While an object exists, it holds on to its box, so nothing else can live in it. If you want to create another object, you’ve got to grab another box from the pile for it to live in, and so on.

Now, if you destroy an object, its box becomes empty again and gets thrown back onto the pile, ready to get used again.

OK, so now what happens if we keep creating objects, but never destroy them when we’re done using them? (Incidently, this is what is known as a memory leak) Well, we keep needing empty boxes to put our objects in. But remember, our device only has a limited amount of memory, so we’re going to run out eventually. And when that happens, it’s game over. A number of things can happen to your application, depending on the device and operating system, including:

An exception is thrown when you try and create a new object when there’s no more space

The operating system can just force your application to close and grab back all of its memory without even notifying you

Nothing happens, but your memory allocation returns an invalid address. If you try and use this address, your application and device might just crash

Obviously, we don’t want any of these to occur, so we’d better make sure we tidy up after ourselves and put the boxes back.

* – The heap is our pile of boxes (i.e. all of our application’s memory)

Simple. We’ve got our function that grabs some memory, and one that puts it back. This is great for basic programs, but in larger applications, you’ll soon realise that you need to be very careful about what you do with that memory. Take the following:

// This function returns a copy of a C-style stringchar* String_Copy(constchar* pString){int SizeOfMemoryNeeded =0;char* pMemoryToReturn = NULL;// Get the length of the string
SizeOfMemoryNeeded =strlen(pString);// Add 1 byte on for the null terminator// strlen returns the number of characters in the string,// but C-style strings need a byte at the end set to 0// to mark where the string stops. (This is known as being null-terminated)
SizeOfMemoryNeeded +=1;// Allocate our memory
pMemoryToReturn =(char*)malloc(SizeOfMemoryNeeded);// Copy our stringstrcpy(pMemoryToReturn, pString);// Return the newly allocated memoryreturn pMemoryToReturn;}int main(){constchar* pMyString ="This is my string";char* pCopiedString = NULL;// Copy MyString
pCopiedString = String_Copy(pMyString);// Now we have a pointer, pCopiedString, which// points at the memory we grabbed in String_Copy.// ....// We pass our copied string into a function
SomeFunction(pCopiedString);// ....// So now we decide we're going to play nice and clean up// our memory that we allocated:free(pCopiedString);}

// This function returns a copy of a C-style string
char* String_Copy(const char* pString)
{
int SizeOfMemoryNeeded = 0;
char* pMemoryToReturn = NULL;
// Get the length of the string
SizeOfMemoryNeeded = strlen(pString);
// Add 1 byte on for the null terminator
// strlen returns the number of characters in the string,
// but C-style strings need a byte at the end set to 0
// to mark where the string stops. (This is known as being null-terminated)
SizeOfMemoryNeeded += 1;
// Allocate our memory
pMemoryToReturn = (char*)malloc(SizeOfMemoryNeeded);
// Copy our string
strcpy(pMemoryToReturn, pString);
// Return the newly allocated memory
return pMemoryToReturn;
}
int main()
{
const char* pMyString = "This is my string";
char* pCopiedString = NULL;
// Copy MyString
pCopiedString = String_Copy(pMyString);
// Now we have a pointer, pCopiedString, which
// points at the memory we grabbed in String_Copy.
// ....
// We pass our copied string into a function
SomeFunction(pCopiedString);
// ....
// So now we decide we're going to play nice and clean up
// our memory that we allocated:
free(pCopiedString);
}

Ownership

So that’s all fine and good. We’ve played nice and put our box back on the pile when we’re done with it. Then one day, after lots of code has been added to our project, suddenly we get BOOM! when we try and free our memory. What gives? Well, after hours of searching, we eventually find this travesty somewhere deep in the program:

void SomeFunctionDeepInTheProgram(char* pAString){// Do some stuff// We don't need the string any more, so lets delete itfree(pAString);}

void SomeFunctionDeepInTheProgram(char* pAString)
{
// Do some stuff
// We don't need the string any more, so lets delete it
free(pAString);
}

Gah! Whoever wrote that should be shot. It turns out that our copied string was being passed into this function, and the function was freeing up its memory ready to be used by something else.

The key here is ownership. Who’s in charge of the memory that was created in String_Copy? Who should be the one that frees it up? One good way to decide this is through the use of certain key words in our function names. We can define a simple rule that we’ll always stick to:

If a function with the word Copy or Create in its name returns memory, then whoever called that function owns the memory.

See how our main() function calls String_Copy? Under our rule, that means that the main() function is responsible for cleaning up that memory, and no one else. SomeFunctionDeepInTheProgram is being naughty, because it’s trying to free up memory that it didn’t get via a Copy or Create function. If everyone follows our simple rule, then we can avoid this situation.

Hopefully that gives you a bit of an introduction into why you need to really think about memory management. Next time we’ll talk about more complex memory management, garbage collectors, reference counting and automatic memory management.