Static memory is what you have seen and used so far: variables (including pointer variables) and arrays of fixed size. You can work with these blocks of memory in your program code using their names as well as their addresses (see Chapter 9, “Working with Pointers”).
With static memory you define the maximum amount of space required for a variable when you write your program:
char a[1000]; // Fixed at runtime.
Whether needed or not, all of that memory will be reserved for that variable and there is no way to change the amount of static memory while the program runs.
Dynamic memory is different. It comes in blocks without names, just addresses. It is allocated when the program runs, in chunks from a large pool that the standard C library manages for you.
You use the standard library's malloc() function to request a block of a certain size—in bytes—from the available memory pool. The size is dynamic and can be determined when the program runs (Figure 10.1):
int memsize; // memsize gets a value at runtime. x = malloc(memsize);
When you are done with the block, you return it to the pool using the free() function:
free(x);
Every call to malloc() must be balanced by a call to free(). A missing or double free() call is considered a bug (specifically, a missing free() statement creates a memory leak).
If enough memory is available to satisfy your request, the malloc() function returns the starting address of the newly allocated block. You usually store this address in a pointer variable for later use.
There is a small problem, though. Remember from the previous chapter that a pointer variable can only hold an address of the appropriate type, that is, a char pointer variable can only hold the address of a char, an int pointer variable can only hold the address of an integer, and so on. However, the blocks of memory whose starting addresses malloc() returns are just that: untyped blocks of memory of the requested size. The type of the data stored in them is usually determined later on in the program, after the malloc() call.
To accommodate addresses of such untyped blocks of memory, C provides a special type of pointer variable called a void pointer. You define such a pointer like so:
void *x;
A void pointer variable can store addresses of any type, including integers, chars, floats, etc. It can also store the addresses of the untyped blocks of memory that malloc() returns:
void *x; x = malloc();
Finally, as an extra precaution, the pointer should be set to NULL after you have freed the associated memory:
void *x;
x = malloc (size);
free(x);
x = NULL;
By taking this final step, the program knows that x no longer refers to a block of memory. This is desired so that any uses of x (unless it is assigned another value beforehand) will fail rather than create hard-to-debug oddities.
Let's write our first dynamic memory example using malloc() and free(). The example is short but introduces the basic syntax that will be used again in all subsequent examples, so it is important for you to understand this process clearly.
1. | Create a new file or project in your text editor or IDE. |
2. | Type the standard beginning lines of code (Script 10.1): /* memory c - Script 10.1 */ #include <stdio.h> #include <stdlib.h> Script 10.1. The malloc() and free() functions are used to allocate and release blocks of dynamic memory.Note the inclusion of the stdlib.h header file. It provides the declarations for the malloc() and free() functions required by the compiler when working with dynamic memory. |
3. | |
4. | Define a void pointer variable: void *x; |
5. | Call the malloc() function to request a block of memory 1000 bytes long: x = malloc(1000); The address returned by the malloc() function—which indicates the starting address of the requested block of memory—will be assigned to the void pointer x. |
6. | Print the address: printf("Starting block address: %p. ", x); To allow you to confirm that the process worked and to view the minimal results, the address stored in the void pointer will be printed. The %p signifier is used, since x is a pointer. |
7. | Return the block to the pool using the free() function: free(x); x = NULL; The free() function takes one argument: the address of the block to be returned to the pool. After this call to free(), the specific block of memory pointed to by the address may no longer be used by the program. In other words, referring to x may cause problems since it no longer points to a reserved block of memory. For this reason, the second line sets the value of x to NULL, so that any inadvertent references to x after this will quite clearly be wrong. |
8. | |
9. | Save the file as memory.c, compile, and debug as necessary. |
10. | Run the application (Figure 10.2). Figure 10.2. The base address of the memory block returned by the malloc() function is printed to confirm that the process worked.So far you didn't actually use the memory, but you'll be getting to that in the next sections. |
✓ Tips
Note that the term static memory used in this section has nothing to do with the static C keyword. The static keyword was introduced in Chapter 7, “Creating Your Own Functions,” and is pertinent to function variables. The term static memory as used in this section means that the amount of memory is set at compile time and cannot be changed during runtime.
The block of memory returned by malloc() may be filled with random garbage. Most of the time, this is not a problem because you usually write to memory first before reading from it. In some situations, though, you might need clean memory that has been overwritten with zeros. Instead of writing a for loop to clear the memory yourself, you can use the calloc() function instead of malloc(). It will prefill the memory block with zeros, but that process will take some time. Because of the additional time needed to clear the memory, you should not needlessly use calloc() instead of malloc().
In case you were curious, the word malloc is short for memory allocation.