Dynamically allocated memory in C is allocated from the heap at runtime using malloc() or one of its sibling functions: calloc() and realloc(). Dynamically allocated memory is de-allocated (returned to the heap) by the free() function. In this course we will do most of our run-time memory management using malloc() and free(). Without further ado, it's RTFM time:
bash-2.05b$ man malloc MALLOC(3) Linux Programmer's Manual MALLOC(3) NAME calloc, malloc, free, realloc - Allocate and free dynamic memory SYNOPSIS #include |
Let's look first at a program
We now look at our first demo program that allocates memory at run-time for a string, initializes the memory, copies into it, and then frees the memory. Let's open
Dynamic memory is anonymous memory. It has no name. For this reason we don't refer to a pointer as the name of the string. Contrast this with stack-allocated local variables (i.e., automatic variables) whose name is bound to their memory for their entire lifetime. Thus we must always have a pointer variable to store the address that is returned by the call to malloc(). Otherwise, we have just allocated memory that is inaccessible. This situation is called a memory leak (a.k.a. creating garbage). Another, more common, way to leak memory is to store the address of the allocated memory in a pointer variable, but accidentally overwrite the value in the pointer variable before you call free(). This common error not only leaks memory but causes free() to produce unpredictable behavior.
GOOD PRACTICE: Once you store malloc'd memory into a pointer, do NOT modify that pointer variable. If you need to iterate through the memory you should declare and use other pointer variables. Keep the original pointer pristine!
RULE OF THUMB: For every call to malloc() there must be an assignment into a pointer variable to avoid leaks AND there will usually be a call to free() on that pointer variable.
malloc() takes only one argument value - a size_t that represents the number of bytes you want allocated. Notice, however, that when we want to malloc an array (multiple contiguous memory cells), we can pass an expression to malloc() that is the number of elements requested multiplied by the size, in bytes, of each element. The product of the two factors is the total bytes requested. This is the common practice. The memory elements allocated by malloc() are guaranteed to be contiguous just like a compile-time array. If there is not enough contiguous memory in the heap for the request, malloc() will return NULL. If the allocation was successful, a pointer to the start of the allocated memory is returned. In either case malloc() always returns a pointer to void (void *) which we will explicitly cast to the proper type before assigning into the pointer variable. Most modern compilers will make the cast for us if we don't but it is standard practice to explicitly supply the cast.
GOOD PRACTICE: After every call to malloc() you should test for NULL before using that pointer.
In our demo we scanf() into our dynamically allocated string using the same syntax as if we were reading into a compile-time array of char. All of C's string functions don't care whether the incoming string is allocated at compile-time (off the stack) or run-time (off the heap). After initializing and echoing the string - we call free() on the pointer variable to return the memory to the heap.
Question #1: Is it really necessary to free that pointer prior to the end of the program?
mallocDemo2.c declares a pointer in main, but we want to initialize (allocate memory for) that pointer inside a function. Following our pattern, if we want to modify the value of the variable, we are required to pass the address of that variable into the function. This is where things get interesting again because the receiving parameter must declared as a pointer to pointer (the address of the pointer variable in main)! This introduces the ** notation.
Our Demo2 accomplishes the same work as Demo1 except it modifies main's word pointer in a function - rather than in main where the pointer was declared. The data type of the incoming argument is thus a pointer to a pointer and its declaration is **. Once this is understood, we then apply our same rules as passing the address of an int. We simply dereference the parameter and use it as a synonym for the value of the original pointer in main. In addition to patterning the behavior, you can pattern the code as well. When passing the address of any variable (&var, as an argument, just use the type of that variable and add a star to the declaration of the receiving parameter. Once inside the function, you dereference that parameter with a single star as a synonym for the original variable that you are trying to modify in the calling scope.
mallocDemo3.c declares an array of pointers, and passes that array into a function that will malloc strings to those pointers. The array of pointers itself is compile-time allocated (and so its length is fixed at compile-time) but the strings that are hanging off each pointer are dynamic.
wordArray[ 0 1 2 3 4 5 6 7 8 9 ] char* char* char* char* char* char* char* char* char* char* | | | | | "foobar" | | | | "foobaz" | | "gorp" "foobag" | "blarp"
Note that when we free the memory associated with this structure we free only the strings hanging off each array cell. We cannot free() the array since it was allocated off the stack (compile-time) and not from the heap (via malloc() at run-time). Only the strings were malloc'd and you can only free what you've malloc'd.
mallocDemo4.c allocates a run-time array of pointers, then allocates "right-sized" malloc'd strings as needed. Note this scheme solves both horizontal and vertical wasted memory problems.
wordArray --> 0 1 2 3 4 char* char* char* char* char* | | | | | "foobar" | | | | "foobaz" | | "gorp" "foobag" | "blarp"
Note that we draw an arrow out of the wordArray variable to the beginning of the array. This is just to emphasize that wordArray is a pointer variable not a normal compile-time array The array itself is run-time allocated as well as the strings hanging off the array. When we free the memory notice that we have to free the strings first and the array itself second.
Question #2: Why do we free the strings first and the array after? What happens if we do it in reverse order?
#define MAX_WORDS 10 #define MAX_WORD_LEN 30 int main() { char wordArray[MAX_WORDS][MAX_WORD_LEN]; /* assume only 5 words ("horses", "cats", "rats", "dogs", "bats") were read into the array */ return 0; } |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 wordArray[0]: ['h']['o']['r']['s']['e']['s'][ Ø ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[1]: ['c']['a']['t']['s'][ Ø ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[2]: ['r']['a']['t']['s'][ Ø ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[3]: ['d']['o']['g']['s'][ Ø ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[4]: ['b']['a']['t']['s'][ Ø ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[5]: [ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[6]: [ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[7]: [ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[8]: [ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ] wordArray[9]: [ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ][ ? ]
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_WORDS 10 int main(int argc, char *argv[]) { char *wordArray[MAX_WORDS]; return 0; } |
wordArray[ 0 1 2 3 4 5 6 7 8 9 ] char* char* char* char* char* char* char* char* char* char* | | | | | "horses" | | | | "cats" | | "bats" "rats" | "dogs"
The disadvantage still remains that the number of pointers allocated is a compile time constant that may be much smaller or larger than the actual size of the input. So, like before,
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_WORD_LEN 30 int main(int argc, char *argv[]) { char word[MAX_WORDLEN]; /* no getting around this! */ char **wordArray; /* pointer to char* - we will malloc an array of char*'s to this ptr at runtime */ return 0; } |
horses cats rats dogs bats
wordArray[ 0 1 2 3 4 ] char* char* char* char* char* | | | | | "horses" | | | | "cats" | | "bats" "rats" | "dogs"