Page 1 of 1

C Memory Management Techniques Rate Topic: -----

#1 stackoverflow  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 165
  • View blog
  • Posts: 545
  • Joined: 06-July 11

Post icon  Posted 11 July 2011 - 07:43 AM

*
POPULAR

C Memory Management Techniques


Disclaimer:

C++ memory management has been addressed in a rather unique and interesting manner. This tutorial is not meant to be a drop-in replacement for the available tutorial (Defeating Mr. Memory Leak by KYA). Nor is this a replacement for the wonderful heap memory management tutorial by Martyn.Rae.

What is this tutorial then?

This tutorial is a C oriented tutorial-- not a C++ oriented tutorial. Furthermore, this tutorial is not Microsoft oriented. This tutorial attempts to stay operating system independent as much as possible.

This tutorial will walk through our standard memory tools as well as provide a few tips to keep in mind while handling memory. In addition this tutorial will provide a list of useful programs to help detect leaks and handle memory. Throughout the tutorial there will be a few side bars. A side bar is extra information you may find useful or you may find it boring and off topic. In either case you can skip a side bar if you wish to strictly stay on topic.

Our Objective

Our objective is to prevent memory leaks. Preventing memory leaks is very important in C and C++. Other memory issues include bounds checking. I will not dive into bounds checking because I don't personally find it to be a memory management problem. I find bounds checking to be a careless programmer issue. However, I do suggest learning about bounds checking as it is very important. Again-- the focus of this tutorial is memory management, in particular, the prevention and detection of memory leaks.

What are our weapons?

When dealing with memory in C we generally have two weapons. The first weapon is a function to allocate memory. C provides a few of these functions such as malloc, calloc, and realloc. Our second weapon is free. Free is used to release the memory and give it back to the operating system. What happens after the operating system gets it is not relevent to us in most cases.

C and C++ differ in terms of their memory weapons. C++ provides the new operation as well as C's allocation functions. Furthermore, C++ provides an additional version of free called delete. However, these are not of concern for us now but they are worth noting.

Side Bar: When is it important to understand how the operating system handles freed memory?

Most naive programmers assume the operating system completely erases memory. That is typically not the case. Most implementations do no bother with erasing memory. Instead, the operating system simply says, "This memory is writeable if we need it.. until then let's just leave it as it was." This can be important to understand.

When can it be important? Well, one example is when you store important data in memory. Let's say we stored a password in memory and later we freed the memory that held the password. A naive programmer would assume the in-memory password is gone. An enlightened programmer would assume the operating system left the password in memory.

The enlightened programmer would then step back and think this is bad. Why? Well anyone with a memory scanner or such utility could easily scanner around the memory looking for the password(s). A good idea would be to overwrite the password with bogus data before freeing it. C provides a number of handy functions for overwriting memory segments or zeroing them out-- such as memset and bzero.

Do we have any more weapons?!

You may assume your compiler is your friend, but when it comes to memory management most compilers don't concern themselves. The C language isn't very friendly either. The language assumes the programmer is right-- even if you do something incredibly odd, the language will assume you know what you are doing. In spite of this there are more weapons. We have additional software we can use to help us out! However, these tools will identify memory errors. Our main ambition should be to avoid memory errors by coding carefully. Even though that is our ambition it is still very important to verify our programs and these softwares can help.

Valgrind (Linux)
Insure++ (Linux and Windows)
Purify (Linux and Windows)

Many IDEs incorperate memory management tools. For instance Eclipse has a Valgrind plugin. A more interesting approach will be when IDEs have fully integrated memory management support. An interesting new C IDE which has such features is TenaciousC. Microsoft has a number of memory management tools in the VS IDE but as I stated before I am not trying to re-write the wonderful Microsoft heap guide!

The tools should be used in addition to good coding techniques. As we have stated before-- tools can only detect errors but they can't detect all errors. Our first tool is to use good techniques so we don't cause errors to begin with. We can sum up good coding techniques with a few rules to follow.

The rules to C memory management.

Rule 1: When you allocate memory you need to eventually deallocate it!

If you have 10 calls to malloc in a program you should have 10 calls to free in the same program. We can simplify this to, if you have X mallocs you need X frees or else you have a memory leak. This means you need to deallocate (free) memory even if the program is going to end.

For example:
#include <stdlib.h>
int main(void)
{
  int *memoryChunk = malloc(sizeof(int) * 100);
  /* we should free(memoryChunk); before returning */
  return 0;
}


This program contains a memory leak. It's pretty simple right?! We allocated memory so we should have a free to match our call to malloc. If memory management was always so obvious we wouldn't have to worry about it! Here's a slightly more complicated and more likely case to go forgotten.

#include <stdlib.h>

typedef struct MyObject MyObject;
struct MyObject{
  int *memoryChunk;
};

MyObject* newMyObject(void){
  MyObject *myobject = malloc(sizeof(MyObject));
  myobject->memoryChunk = malloc(sizeof(int) * 10);
  /* 2 calls to malloc */
  return myobject;
}

int main(void)
{
  MyObject *myobject = newMyObject();

  free(myobject); /* hah! we remembered to free it! right? No. */
  return 0;
}


In this case we called malloc 2 times but called free only once. We clearly violated rule 1! That means a memory leak exists. Where? Well, we remembered to free myobject in main, but we didn't free the other memory chunk that is inside of myobject. Here's a simple way to fix this error:

#include <stdlib.h>

typedef struct MyObject MyObject;
struct MyObject{
  int *memoryChunk;
};

MyObject* newMyObject(void){
  MyObject *myobject = malloc(sizeof(MyObject));
  myobject->memoryChunk = malloc(sizeof(int) * 10);
  /* 2 calls to malloc */
  return myobject;
}

void freeMyObject(MyObject *myobject){
  free(myobject->memoryChunk);
  free(myobject);
  /* 2 calls to free */
}

int main(void)
{
  MyObject *myobject = newMyObject();

  freeMyObject(myobject);
  return 0;
}


We fixed it by making sure we called free on the inner allocated memory before we call free on the main allocated memory. This brings us to rule 2.

Rule 2: When you have a nested structure or complex mix of allocations it may be a good idea to create a wrapper around free to ensure all allocations are freed.

This can be seen in the example above. Another example is within linked lists or other complex structures. In a linked list you must preserve references before you delete and node and free memory. If you carelessly change references without freeing the old nodes you will create a memory leak. In cases like this and cases like the above example-- it may be a great idea to wrap up these complex deallocations in a function to ensure they are done safe and consistently.

I won't go into much more detail about data structures-- they deserve tutorials on their own and there are a few great ones available.

Rule 3: Watch out for functions that allocate memory but don't deallocate it!

This is in addition to rule 1. Actually, this is rule 1 in a ninja suit. There are a number of functions which allocate memory and don't let you know! An example is within the C standard library.

#include <stdlib.h>
#include <string.h>

int main(void){
  char *oldString = "I'm old!!!!";
  char newStrig = strdup(oldString);
  /* we need to call free(newString); */
  return 0;
}


In this case we can't see that memory has been allocated. It has been allocated behind our backs! When in doubt you should read man pages or other information regarding the standard library function. It's important to understand if the function is allocating memory behind your back. If it is-- we need to free it in order to not violate rule 1.

Rule 4: If you doubt your counting ability, make a count variable to keep track of how many objects/allocations have been made.

This is a debugging technique which may be helpful. Instead of trying to count how many calls to free you made in a large program-- you can toss in a variable that keeps track of calls to malloc. Then while you use a debugger you can keep track of how many calls are made to allocate memory at any given point in the program. Here's an overly simple example.

#include <stdio.h>
#include <stdlib.h>
#define DEBUG

#ifdef DEBUG 
int MyObjectAllocations = 0;
#endif

typedef struct MyObject MyObject;
struct MyObject{
  int *memoryChunk;
};

MyObject* newMyObject(void){
  MyObject *myobject = malloc(sizeof(MyObject));
  myobject->memoryChunk = malloc(sizeof(int) * 10);
  /* 2 calls to malloc */
  #ifdef DEBUG 
  MyObjectAllocations += 2;
  #endif
  return myobject;
}

void freeMyObject(MyObject *myobject){
  free(myobject->memoryChunk);
  free(myobject);
  /* 2 calls to free */
  #ifdef DEBUG 
  MyObjectAllocations -= 2;
  #endif
}

int main(void)
{
  MyObject *myobject1 = newMyObject();
  MyObject *myobject2 = newMyObject();

  #ifdef DEBUG
  printf("\nMyObjectAllocations here... %d\n", MyObjectAllocations);
  #endif

  freeMyObject(myobject1);

  #ifdef DEBUG
  printf("\nMyObjectAllocations here... %d\n", MyObjectAllocations);
  #endif

  freeMyObject(myobject2);

  #ifdef DEBUG
  printf("\nMyObjectAllocations here... %d\n", MyObjectAllocations);
  #endif
  return 0;
}


It isn't the most attractive strategy, but it is something you can do all on your own without extra software. If you want a more attractive debugging method you can use better preprocessor directives and macros to make it much better. I tried to keep it simple so the tutorial example is understandable by everyone. The key point to take away from it is-- you can use coding techniques to help keep track of allocations.

Side Bar:
The preprocessor is amazing and enables you to make some impressive debugging statements that don't inject operations and size into your program's finished version. I suggest you look at a few resources and see if you can build a small debugging library of your own.

The C Preprocessor
The C preprocessor tutorial
Wikipedia (has a nice section on debug print statements)

Conclusion

We can conclude memory management with a recap of our techniques. The golden technique when dealing with memory is to count your allocations and ensure you have an equal count of deallocations. The silver rule is to watch out for allocations done behind your back. The bronze medal goes to debugging tools and techniques that can help catch problems after they have occured. We have the techniques-- use em!


Exercises

I like to end a guide or tutorial with code samples or problems to solve. If I did a good job explaining memory management then the following exercises should be easy to solve. If you have questions concerning them then please ask-- it probably means I didn't explain something clearly enough.

Which of the following examples have memory leaks? If a leak exists where is it?

Exercise 1:
#include <stdlib.h>

typedef struct Node Node;
struct Node{
  Node *next;
  Node *previous;
};

int main(void){
  Node *node = malloc(sizeof(Node));
  free(node);
  return 0;
}


Exercise 2:
#include <stdlib.h>

typedef struct Container Container;
struct Container{
  void *myBox;
};

int main(void){
  Container *container = malloc(sizeof(Container));
  container->myBox = malloc(20);
  free(container->myBox);
  return 0;
}


Exercise 3:
#include <stdlib.h>

typedef struct Station Station;
struct Station{
  int *seats;
  Station *previousStation;
};

Station* newStation(int size, Station* previousStation){
  Station *station = malloc(sizeof(Station));
  station->seats = malloc(sizeof(int) * size);
  station->previousStation = previousStation;
  return station;
}

void freeStation(Station* station){
  free(station->seats);
  free(station);
}

int main(void){
  Station *stn1 = newStation(10, NULL);
  Station *stn2 = newStation(20, stn1);
  freeStation(stn1);
  return 0;
}


Is This A Good Question/Topic? 9
  • +

Replies To: C Memory Management Techniques

#2 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 1000
  • View blog
  • Posts: 4,181
  • Joined: 14-February 08

Posted 11 July 2011 - 02:51 PM

Nice work! Really interesting points :) keep it up.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1