Speak softy, and carry a big fork

Fork is one of the many powerful tools you have at your disposal as a C developer. Take for example the code below. Here was an honest attempt at implementing an example using fork, but things went very wrong.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

// main takes two parameters: argc is the number of command-line
// arguments; argv is an array of strings containing the command
// line arguments
int main (int argc, char *argv[])
{
    int status;
    pid_t pid;
    int i, num_children;

    // the first command-line argument is the name of the executable.
    // if there is a second, it is the number of children to create.
    if (argc == 2) {
        num_children = atoi (argv[1]);
    } else {
        num_children = 1;
    }

    for (i=0; i<num_children; i++) {

        // create a child process
        printf ("Creating child %d.\n", i);
        pid = fork ();

        /* check for an error */
        if (pid == -1) {
            fprintf (stderr, "fork failed: %s\n", strerror(errno));
            perror (argv[0]);
            exit (1);
        }
    }

    /* see if we're the parent or the child */
    if (pid == 0) {
        printf ("Hello from child %d.\n", i);
    }

    /* parent continues */
    printf ("Hello from the parent.\n");

    exit (0);
}

The code is supposed to take in a command line argument of the number of processes to create, but it doesn’t quite behave as expected. What’s wrong it? It may not be obvious from a first pass, but I recommend reading through and figuring out what this code actually does. Run the code with different arguments to get a better idea of what’s happening.

This behavior is the fundamental idea behind an exploit sometimes seen in malicious software called a fork bomb. The code in a fork bomb infinitely spawns new processes, starving the system of resources and effectively crashing it. The simplest fork bomb in C looks like this:

#include <unistd.h>

// WARNING! Run at your own risk!
int main(void)
{
    while(1)
        fork();
}

Unit Testing in C

Like any good software developer, I’m sure you’re beginning to wonder how to write sustainable C code. The way to do that in any language is with unit tests. The idea behind unit tests is to have each “unit” of your code be validated with assertive tests. A “unit” is typically a single function, and the goal of unit tests on that function is to validate that the function behaves as you expect for a range of inputs.

While there are many unit testing frameworks for C, for the purposes of this article I’ll be using minunit. Minunit is an incredibly simple framework that makes learning how to unit test easy. In fact, the source code is a whole 3 lines (seen below).

To install minunit, copy the below code into a “minunit.h” file. It doesn’t matter where it’s located in your file system, as long as you know how to include it in your unit test files.

#define mu_assert(message, test) do { if (!(test)) return message; } while (0)
#define mu_run_test(test) do { char *message = test(); tests_run++; \
                               if (message) return message; } while (0)
extern int tests_run;

Minunit provides two critical macros, mu_assert and mu_run_test. mu_assert takes a message and a statement that evaluates to a boolean value (i.e. my_int == 5), returning the message if the boolean value is false. mu_run_test handles calling the functions containing all of your mu_assert calls.

A simple test suite using minunit looks something like this:

#include <stdio.h>
#include "minunit.h"

int tests_run = 0;

// Here's a sample function we'll test
int add(int x, int y) {
    return x + y;
}

// Here's another sample function,
// except this one has an error (* instead of +)
int bad_add(int x, int y) {
    return x * y;
}

// Testing the add() function
static char * test_add() {
    mu_assert("error, adding 2 and 3 != 5", add(2, 3) == 5);
    return 0;
}

// Testing the bad_add() function
static char * test_bad_add() {
    mu_assert("error, adding 2 and 3 != 5", bad_add(2, 3) == 5);
    return 0;
}

// Here is where we call all of the unit test functions
static char * all_tests() {
    mu_run_test(test_add);
    mu_run_test(test_bad_add);
    return 0;
}

int main(int argc, char **argv) {
    char *result = all_tests();
    // result will be the failed test message if a test fails
    if (result != 0) {
        printf("%s\n", result);
    }
    else {
        printf("ALL TESTS PASSED\n");
    }
    printf("Tests run: %d\n", tests_run);

    return result != 0;
}

test_add and test_bad_add are the unit testing functions in this example. They should contain 1 or more calls of mu_assert that should test something regarding the functionality of add and bad_add, respectively. A good unit test should have a representative sample of the range of possible inputs as well as a representative sample of the expected domain of the function being tested.

As a note, the above code does not do this well. For example, if I had chosen to have the assertions testing adding 2 and 2, both unit tests would have passed because the bad_add function would find 2 * 2 = 4, which is the expected result as well.

In minunit, there is no automation regarding running the test suite. As such, you have to call mu_run_test on each unit test function, which is aggregated in the all_tests function in the above sample.

While the functionality of minunit is limited, I think it makes a good place to start with unit testing. When you feel comfortable with the idea of unit testing, I’d recommend going and finding another, more feature-rich alternative. You can find an excellent discussion regarding the available choices on Stack Overflow.

Catching Bugs in C with Lint

As you all have probably realized by now, C is not known for being a friendly language. In the best case, development is slow for even the simplest of tasks, and in the worst case… well… you’ve probably never been more tempted to through your laptop out the window. A lot of the frustrations with C come from the difficulty of fixing bugs in your code. The error messages can be cryptic unless you have a thorough understanding of the inner workings of your machine. Even then, the error messages may lead you down the wrong path of how to fix your code.

One tool that helps identify issues in your code in between making your change and compiling your code is lint. A linter is a static code checker which evaluates your code without compiling it. You can think of it as an extra set of eyes on your code – a set of eyes that doesn’t miss silly mistakes like memory leaks or uninitialized variables.

Linters exist for pretty much every language out there, and functionality between different tools range from language specific gotchas to styling. The original C lint program has had most of its functionality wrapped into the various C compilers over the years, but other tools have come and improved upon its legacy. One highly recommended option for C is cppcheck (works for both C and C++).

sudo apt-get install cppcheck

To check a file, simply run cppcheck on the file.

cppcheck myfile.c

As an example of the power of cppcheck, here’s a short program with a memory leak.

#include <stdlib.h>

void foo(int size) {
    int *array_pointer;
    array_pointer = (int *)malloc(size*sizeof(int));
    int i;
    for (i = 0; i < size; ++i)
    {
        array_pointer[i] = i;
    }
}

int main() {
    foo(10);
}

What does cppcheck say about this program?

jgibson@ubuntu:~$ cppcheck debugging_exercise.c 
Checking debugging_exercise.c...
[debugging_exercise.c:13]: (error) Memory leak: array_pointer

Cppcheck tells you exactly where the memory leak is, making it quick and simple to fix.

Cppcheck is not perfect. For example, if you add a printf statement at the end of foo(), it tricks cppcheck into not finding the memory leak, even though it is still there.

#include <stdlib.h>
#include <stdio.h>

void foo(int size) {
    int *array_pointer;
    array_pointer = (int *)malloc(size*sizeof(int));
    int i;
    for (i = 0; i < size; ++i)
    {
        array_pointer[i] = i;
    }
    printf("Array at index 6 is %i!\n", array_pointer[6]);
}

int main() {
    foo(10);
}
jgibson@ubuntu:~$ cppcheck debugging_exercise.c 
Checking debugging_exercise.c...

Cppcheck won’t catch all of the problems with your code, but it will help catch a large portion of them before you have to deal with C’s atrocious error messages.

For more info on cppcheck and how to use it, check out their documentation. You can look through their complete list of what they check for as well as some of the more advanced features such as ignoring certain warnings.