Finding Memory Errors in C

Memory errors are among the nastier programming mistake one can make when using C. Unlike syntax errors, the compiler doesn’t flag them for you. Unlike basic logic errors, you won’t necessarily see the problem in the output. The result – silent data corruption – usually manifests itself differently under different systems and compilers which makes it especially obnoxious to deal with.

Fortunately, there are a couple of good tools for debugging memory errors. My favorite is valgrind, a memory debugger modeled after Rational’s purify (the commercial standard) that runs on Linux and now (experimentally) on MacOS X.

Basically, valgrind runs your application in a sandbox, allowing it to verify each memory access before it is made. It doesn’t require special compiler support, although debugging information will make the output more useful. In addition to detecting invalid reads and writes, valgrind keeps track of allocations, notifying you of potential memory leaks. The only real downside is that applications runs significantly more slowly in valgrind than directly.

Here’s a simple example of a program with several errors.

#include <stdlib.h>

typedef struct { char *name; int count; } S;

int main(int argc, char *argv[])
{

int *a = malloc(3);
int i;
S *s = malloc(sizeof(S));
s->name = malloc(10);

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

a[i] = 0;

}
free(a);
free(s);
return 0;

}

Now if you compiler this code with all warning enabled in gcc, you won’t get any messages. And indeed if you run it on many systems, you won’t get any errors either.

However, run it in valgrind, and you’ll get some useful information. In particular, you get the line numbers for both the invalid writes (test.c:14) and the leaked memory (test.c:10).

$ gcc -g -Wall test.c -o test
$ valgrind --leak-check=full ./test
==20405== Memcheck, a memory error detector
==20405== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20405== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20405== Command: ./test
==20405==
==20405== Invalid write of size 4
==20405== at 0x8048446: main (test.c:14)
==20405== Address 0x401d028 is 0 bytes inside a block of size 3 alloc'd
==20405== at 0x4005BDC: malloc (vg_replace_malloc.c:195)
==20405== by 0x8048408: main (test.c:7)
==20405==
==20405==
==20405== HEAP SUMMARY:
==20405== in use at exit: 10 bytes in 1 blocks
==20405== total heap usage: 3 allocs, 2 frees, 21 bytes allocated
==20405==
==20405== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==20405== at 0x4005BDC: malloc (vg_replace_malloc.c:195)
==20405== by 0x8048428: main (test.c:10)
==20405==
==20405== LEAK SUMMARY:
==20405== definitely lost: 10 bytes in 1 blocks
==20405== indirectly lost: 0 bytes in 0 blocks
==20405== possibly lost: 0 bytes in 0 blocks
==20405== still reachable: 0 bytes in 0 blocks
==20405== suppressed: 0 bytes in 0 blocks
==20405==
==20405== For counts of detected and suppressed errors, rerun with: -v
==20405== ERROR SUMMARY: 4 errors from 2 contexts (suppressed: 12 from 8)

While it’s not as convenient, you can run your codein the Visual Studio debugger in Windows to detect most invalid reads and writes. There are also means of doing leak detection, although it requires installing some optional libraries and components.

Comments are closed.