Sage-Code Laboratory
index<--

C Memory Management

When you create a program one of your concerns will be to define and organize data in computer memory. Computer memory is expensive and limited resource. You must design your data structures such way that you use a limited amount of memory and avoid wasting precious memory during your computations.

Management methods

Standard C language has 3 methods of memory management:

  1. Static memory allocation,
  2. Automatic memory allocation,
  3. Dynamic memory allocation.

Static memory allocation:

This is usually allocated on the heap. Using this kind of allocation required fixed size objects. Static memory has little overhead. It is used for constants and global variables of fixed capacity.

Automatic memory allocation:

This is used to create transient objects defined in local scope of a function or block. These objects are stored on the stack and removed when the block terminate. It also required fixed size objects.

Dynamic memory allocation:

Sometimes we do not know how large an object will be until the program is running. When the size is known we can allocate memory using a function call with parameters. We need to calculate the memory required before we call memory allocation function.

After we use the object and is no longer necessary to keep, we can remove object from memory using a function call. If we free objects still in use the program can become unpredictable. Compilers usually do not detect these kind of errors.

Dynamic memory allocation has overhead. Therefore it can slow down the program. Sometimes the memory allocation can fail, so we have to check if the allocation was successful before we can setup and use the object we have created.

Note: So far we have learned already how to use static memory allocation and automatic memory allocation. This is the default method used by C when we define global and local variables. Next you will learn dynamic memory allocation. This require explicit call for specific functions and is considered manual memory management.

Allocation functions

Dynamic memory allocation functions are found in <stdlib.h> header file. You must include this header into your application to be able to allocate and reallocate memory for dynamic objects. There are 4 important memory allocation functions:

  1. malloc() – allocates a specified number of bytes,
  2. calloc() – allocates the specified number of bytes and initializes them to zero,
  3. realloc() – modify the size of the specified block of memory, moving data if necessary,
  4. free() – releases the specified block of memory back to the system.

Example: for malloc and free

#include <stdio.h>
#include <stdlib.h>
int main() {
    // dynamic array allocation
    int *array = malloc(10 * sizeof(int));
    if (array == NULL) {
    fprintf(stderr, "malloc failed\n");
        return(-1);
    }
    // array initialization
    int i = 0;
    for (i = 0; i < 10; i++) {
        printf("array[%d] = %d\n", i, array[i]);
    }
    free(array);
    return(0);
} 

Note: Observe that we do not assign any value to array elements. This is bad practice. Most of the time the value will be 0 and you may think the program is fine. However sometimes the elements may have undetermined value and should be initialized.

Example: for calloc() and realloc()

#include <stdio.h>
#include <stdlib.h>
int main() {
    // dynamic array allocation
    int *array = calloc(5, sizeof(int));
    if (array == NULL) {
        fprintf(stderr, "calloc failed\n");
        return(-1);
    }
    // array traversal
    int i = 0;
    for (i = 0; i < 5; i++) {
        printf("array[%d] = %d\n", i, array[i]);
    }
    // resize array
    array = realloc(array, 10 * sizeof(int));
    for (i = 0; i < 10; i++) {
        printf("array[%d] = %d\n", i, array[i]);
    }
    // remove the array from memory
    free(array);
    return(0);
} 

Note: realloc() like maloc() do not initialize the new array elements. So if you do not, the value is not predictable. It may be garbage. Therefore is a good practice to give value to new elements.

Dynamic Allocation

Sometimes we do not know how much memory to allocate, it can be users decision. In this case we must allocate memory after user input. In next example we organize a group of persons in memory using consecutive pointers.

Example:

#include <stdio.h>
#include <stdlib.h>
/* define structure person */
typedef struct {
    int id;
    char name[20];
    int age;
} Person;
int main()
{
    Person *p;
    int i, n;
    printf("Number of persons: ");
    scanf("%d", &n);
    /* dynamic allocation */
    p = malloc(n * sizeof(Person));
    for(i = 0; i < n; ++i)
    {
        (p+i)->id = i+1;
        printf("ID: %d\n", (p+i)->id);
        printf(" name:");
        scanf("%s", &(p+i)->name);
        printf(" age:");
        scanf("%d", &(p+i)->age);
    }
    printf("\n");
    printf("List of persons:\n");
    for(i = 0; i < n; ++i)
        printf("ID: %d Name: %-20s\tAge: %d\n", (p+i)->id, (p+i)->name, (p+i)->age);
    return 0;
}

Note: At the end of program the memory is free automatically. Normally we should use free after we have used all the persons. For simplicity in this example we do not clean-up the memory.

Possible Mistakes

Allocation failures
Memory allocation is not guaranteed to succeed, and may instead return a null pointer. Using the returned value, without checking if the allocation is successful, invokes undefined behavior. This usually leads to crash, but there is no guarantee that a crash will happen.

Memory leaks
Failure to free memory leads to buildup of non-reusable memory, which is no longer used by the program. This wastes memory resources and can lead to allocation failures when these resources are exhausted.

Logical errors
All allocations must follow the same pattern: allocation using malloc, usage to store data, de-allocation using free. Failure to implement this pattern can result in logical errors. Most common:

Warning: These errors can be transient and hard to debug. For example, freed memory is usually not immediately reclaimed by the OS, and thus dangling pointers may persist for a while and appear to work when you test it. Only careful code review will reveal your mistakes.

Read next: Error Handling