C Programming/Memory Management

Objective

 * Distinguish between stack-based and heap-based memory allocation.
 * Learn how to allocate memory.
 * Learn how to release memory.
 * Learn how to resize memory.
 * Learn how to allocate "clean" memory.
 * Be aware of memory management problems.

Memory
So far, you probably haven't given much thought about how a program works or how it runs. Well, most of us know that a program is a file that is loaded into memory at some undefined point. So given the following code below, where are,  , and   kept?

,, and   are all kept on the stack, which is a fancy word for a small memory region where temporary variables are added and removed in a special way. Everything is added and removed starting at the top, much like a stack of cards. You usually take cards off the top and sometimes you might put them on the top; these stack operations are called pop and push respectively. So we can use deductive reasoning to say that  is pushed onto the stack, followed by   and. The great thing about allocating memory on the stack is its ease of use. The memory is allocated rather fast with minimal overhead costs. When you're done using the memory, the memory is automatically released. This means we don't need to manage the memory.

Although this is a great way to allocate memory, it was mentioned that the stack is usually small, meaning you don't want to put large variables onto the stack; mainly large arrays and large structures. If you don't want to waste your precious stack space, where and how can you allocate large amounts of memory? Luckily, there's a large memory region reserved for just that. This area is called the heap. Essentially, the heap is a very large memory pool where certain parts of it may be used or unused. Thankfully, the operating system takes care of the complex task of managing it; all we need to know is how to allocate the memory and release it. Unfortunately, if you don't release the allocated memory, it will remain allocated until you do. This could lead to a memory leak, in which you lose the location of the allocated memory and you'll never be able to release it. This could impact performance and eventually could cause your program to run out of memory, thereby crashing it. One needs to be extra cautious when working with dynamic memory allocation.

All in all, allocating memory in the heap is great for large variables, while using the stack is best suited for small variables.

Allocating Memory
Before we get started with some examples, it's important to know that all of the memory allocation functions are kept in the header file. The first function we'll be working with is  which stands for memory allocation. This function takes one parameter, the size of the allocated memory in. is basically the largest number used for addressing memory on a computer, so for example, on a 32-bit computer  would be able to express 4 GiB.

Now that we know how to allocate memory, let's try it.

We just made a ten byte dynamic array, but to avoid errors you should always multiply the number of variables you want by the size of the variable's data type. Take a look at the next example and notice the use of the  operator.

is now guaranteed to hold ten  types. Using the  operator allows us to keep our code portable and platform-independent, since the number of actual bytes don't concern us.

Sometimes there just isn't enough memory, meaning that  isn't guaranteed to return a pointer to usable memory. If it isn't able to allocate memory, it will return a null pointer,. If you allocate memory, you should always check to make sure you got usable memory as shown in the following example.

Still, this code needs one final touch-up. returns a  pointer, so it should be cast to the appropriate data type as shown below.

Releasing Allocated Memory
Now that you've allocated memory, your program will keep it as long as it lives. That means allocating large chunks of memory can drain your computer's resources. At some point you're going to want to give back that memory. The  function can help us here. It deallocates the allocated memory, therefore allowing the computer to allocate more later on. Using  is simple; all it takes is one parameter, the pointer to the allocated memory. A demonstration is given below.

Resizing Allocated Memory
Well, it's great that we can allocate and deallocate memory, but what if we want to change the size of the allocated memory? It's possible to do this via the  function. This function takes two parameters, the pointer to the allocated memory and its new size. Depending on unseen variables, the  could change the location of the original. Luckily, it returns the new location for us.

As you probably noticed  returns a void pointer which we should cast into our variable's type. It should be assumed any new memory from  isn't set to zero. If you resize the allocated memory smaller than it was before, it will cause the end of the allocated memory to be truncated.

Allocating Clean Memory
Using  to allocate our memory is great, but it still has its flaws. doesn't worry about the state of the allocated memory, meaning that each byte could have an undefined value. This can be problematic with the array we've been using. When it's allocated, the array may contain undefined values. Usually, when we allocate an array we want everything to be zero. Fortunately, the last function discussed in this lesson allocates memory and guarantees that all of the memory is set to zero. This function is called  and it takes two parameters: the number of items that you want allocated and their size. is demonstrated in the example below.

The output should be: 0: 15 1: 7 2: 0 3: 23 4: 0

Memory Management Problems
Unlike some newer languages, C requires you to manually allocate and deallocate memory. Thus, the task of managing your program's memory falls solely on you, the programmer. No matter how much experience you have with memory management, you'll still make mistakes when working with dynamic memory. The important thing is to realise the types of problems you may encounter and understand how to correct and prevent them. Luckily, a good number of these problems will be discussed in this section.


 * Memory Leaks
 * This is the most well known problem you'll encounter when working with memory. A memory leak occurs when you lose a pointer to allocated memory. Since you no longer know of its location, you aren't able to deallocate the memory. This leads to a chain reaction in which you won't be able to use that memory for the rest of the program's life, eventually the program could completely crash and burn. An example of such a memory leak is given below.


 * See how only the second allocated memory was deallocated and not the first? If a long living program slowly leaked memory, eventually it wouldn't be able to allocate any more, because the memory available for allocation to the program has diminished. This is one of the most common memory problems you'll encounter and is usually easy to correct. The best way to combat memory leaks is to make sure that you always deallocate allocated memory at the end of the pointer's scope, for example the end of a function.


 * Dangling Pointers
 * These pointers can cause a lot of problems in C. A dangling pointer is a pointer that points to a memory region that was previously deallocated. Unfortunately, having a pointer that points to a now freed memory region will surely cause problems, if used. Here is an example of a dangling pointer.


 * The best way to fight dangling pointer is to:


 * 1) Just don't use them! This is easier said than done, especially in large applications.
 * 2) Set the dangling pointer to  . By comparing against , you now know if the pointer's good for use.


 * Don't forget that these steps must be done on every pointer to any recently freed memory region.


 * Unavailable Memory
 * Another common mistake addressed earlier is the assumption that  will always returns a pointer to valid memory, like in this example.


 * You should learn from this example and always check to see if you've received . Assuming that the newly allocated memory is valid will lead to a crash. If you feel it's too time consuming to type up such safety, try using a macro to shorten the amount of code needed. This can save time and money, while reducing the amount of redundancy in your code.


 * External Fragmentation
 * External Fragmentation.svg
 * This problem is really unfair to programmers, even if they did everything right. External fragmentation happens when allocated memory within other allocated memory is deallocated. Only something of that exact size or smaller will be able to fit into the newly deallocated memory region. This can cause a program to run out of memory even if there's lots available. An example is shown on the right.


 * It's up to allocator to be able to avoid this kind of problem. Sometimes, custom memory managers are used to avoid problems like this, though this problem is really just out of your hands.


 * Other Problems
 * Other problems of less importance still exist. It's important to be wary of creating dangling global pointers and elsewhere. Generally, many other problems depend on your memory manager and how well it works. All in all, memory management problems won't likely hinder any part of this course.

Assignments

 * Find a real life item that acts like a stack. For example, a deck of cards, a tube of chips, a money roll, and even a pile of paper can all act like a stack.
 * Reason with yourself the differences between stack-based and heap-based memory allocation.
 * Write a program that demonstrates the use of,  ,  , and  . Don't forget to check your pointers after allocating memory. Be sure the program handles any other kinds of problems.
 * Recite the most common memory management problems.
 * Identify a memory management problem and come up with your own solution.
 * Critical Thinking: Is a pointer allocated on the stack or in the heap?


 * Previous Lesson: Input and Output
 * Next Lesson: Preprocessors
 * Course Home Page