Introduction to C: Fixing Memory Leaks

In the code for the last lesson, there is a subtle bug. Take a close look at the while() loop and see if you can find it:

    while(playing) {
        printf("> ");
        line = input_getline();
        if(strcasecmp("quit", line) == 0) {
            playing = false;
        } else if(strcasecmp("status", line) == 0) {
            player_print(player);
        } else if(strcasecmp("help", line) == 0) {
            printf("Available commands:\n");
            printf("    status - Show player status.\n");
            printf("    help   - Show this help screen.\n");
            printf("    quit   - Exit the program.\n");
        } else {
            printf("Unknown command. Type \"help\" for help, \"quit\" to exit the program.\n");
        }
    }

If you still aren't sure where the bug is, that's okay. In this lesson, we'll demonstrate how to use a tool called valgrind.

Using valgrind

First you'll need to make sure valgrind is installed. (How to install it will vary based on your operating system; I used snap install valgrind --classic to get the latest version.)

To use valgrind with our prgram, we'll simply build it (making sure we're using -g to include debug symbols), and execute our program under its control as follows:

$ make clean
rm -f main
rm -f *.o
$ make
cc -Wall -Werror -std=c11 -g -c player.c -o player.o
cc -Wall -Werror -std=c11 -g -c main.c -o main.o
cc -Wall -Werror -std=c11 -g -c input.c -o input.o
cc -o main *.o
$ valgrind ./main
==14284== Memcheck, a memory error detector
==14284== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14284== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14284== Command: ./main
==14284== 
What is your name?
> 

So far, so good. Let's test our program as normal.

What is your name?
> Mike
Player name: Mike
Level: 1
Health points: 10/10
> status
Player name: Mike
Level: 1
Health points: 10/10
> help
Available commands:
    status - Show player status.
    help   - Show this help screen.
    quit   - Exit the program.
> quit
==14284== 
==14284== HEAP SUMMARY:
==14284==     in use at exit: 360 bytes in 3 blocks
==14284==   total heap usage: 7 allocs, 4 frees, 2,552 bytes allocated
==14284== 
==14284== LEAK SUMMARY:
==14284==    definitely lost: 360 bytes in 3 blocks
==14284==    indirectly lost: 0 bytes in 0 blocks
==14284==      possibly lost: 0 bytes in 0 blocks
==14284==    still reachable: 0 bytes in 0 blocks
==14284==         suppressed: 0 bytes in 0 blocks
==14284== Rerun with --leak-check=full to see details of leaked memory
==14284== 
==14284== For lists of detected and suppressed errors, rerun with: -s
==14284== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

As you can see, when the program begins and ends, valgrind will print out some diagnostics. Notice that under LEAK SUMMARY, we've definitely lost 3 blocks.

Digging Deeper

As valgrind suggests, we can re-run our program with --leak-check=full to get more information. Let's do that now.

$ valgrind --leak-check=full ./main
...
==14514== 
==14514== HEAP SUMMARY:
==14514==     in use at exit: 360 bytes in 3 blocks
==14514==   total heap usage: 7 allocs, 4 frees, 2,552 bytes allocated
==14514== 
==14514== 360 bytes in 3 blocks are definitely lost in loss record 1 of 1
==14514==    at 0x4C2FE96: malloc (vg_replace_malloc.c:309)
==14514==    by 0x4EBCB8B: getdelim (iogetdelim.c:62)
==14514==    by 0x10892A: input_getline (input.c:13)
==14514==    by 0x108A00: main (main.c:24)
==14514== 
==14514== LEAK SUMMARY:
==14514==    definitely lost: 360 bytes in 3 blocks
==14514==    indirectly lost: 0 bytes in 0 blocks
==14514==      possibly lost: 0 bytes in 0 blocks
==14514==    still reachable: 0 bytes in 0 blocks
==14514==         suppressed: 0 bytes in 0 blocks
==14514== 
==14514== For lists of detected and suppressed errors, rerun with: -s
==14514== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

This time, valgrind prints a traceback that shows where some memory was allocated, but never freed.

Fixing the Problem

Let's find out where in our code the problem occurs. The output from valgrind mentions main (main.c:24), and input_getline (input.c:13). That means the allocation that was never freed happened when calling those functions (on the specified line numbers). We'll use cat -n to see which specific lines those are:

$ cat -n main.c | grep 24
    24          line = input_getline();
$ cat -n input.c | grep 13
    13      result = getline(&line, &buffer_size, stdin);

As you can see, the problem occurs inside the while loop. Our input_getline() function allocates memory for the line, but never frees it. So the fix is simple: at the bottom of the while() loop, we'll free the memory.

    while(playing) {
        printf("> ");
        line = input_getline();
        /* ... */
        free(line);
    }

Verifying the Fix

Next, we can re-run with valgrind and confirm that the problem has been fixed:

$ make
cc -Wall -Werror -std=c11 -g -c main.c -o main.o
cc -o main *.o
$ valgrind --leak-check=full ./main
==14912== Memcheck, a memory error detector
==14912== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14912== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14912== Command: ./main
==14912== 
What is your name?
> Mike
Player name: Mike
Level: 1
Health points: 10/10
> status
Player name: Mike
Level: 1
Health points: 10/10
> help
Available commands:
    status - Show player status.
    help   - Show this help screen.
    quit   - Exit the program.
> quit
==14912== 
==14912== HEAP SUMMARY:
==14912==     in use at exit: 0 bytes in 0 blocks
==14912==   total heap usage: 7 allocs, 7 frees, 2,552 bytes allocated
==14912== 
==14912== All heap blocks were freed -- no leaks are possible
==14912== 
==14912== For lists of detected and suppressed errors, rerun with: -s
==14912== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Notice that valgrind now reports that All heap blocks were freed -- no leaks are possible. Great news!

Conclusion

By now, you should know how to use valgrind to check for memory leaks in your program.

In the next lesson, we'll cover arrays, structure initialization, increment operators, and the basic use of gdb.