Exercise 4: Introducing Valgrind
It's time to learn about another tool you will live and die by as you learn C called Valgrind. I'm introducing Valgrind to you now because you're going to use it from now on in the "How To Break It" sections of each exercise. Valgrind is a program that runs your programs, and then reports on all of the horrible mistakes you made. It's a wonderful free piece of software that I use constantly while I write C code.
Remember in the last exercise that I told you to break your code by removing one of the arguments to printf? It printed out some funky results, but I didn't tell you why it printed those results out. In this exercise we're going to use Valgrind to find out why.
These first few exercises are mixing some essential tools the rest of the book needs with learning a little bit of code. The reason is that most of the folks who read this book are not familiar with compiled languages, and definitely not with automation and helpful tools. By getting you to use make and Valgrind right now I can then use them to teach you C faster and help you find all your bugs early.
After this exercise we won't do many more tools, it'll be mostly code and syntax for a while. But, we'll also have a few tools we can use to really see what's going on and get a good understanding of common mistakes and problems.
You could install Valgrind with the package manager for your OS, but I want you to learn to install things from source. This involves the following process:
- Download a source archive file to get the source.
- Unpack the archive to extract the files onto your computer.
- Run ./configure to setup build configurations.
- Run make to make it build, just like you've been doing.
- Run sudo make install to install it onto your computer.
Here's a script of me doing this very process, which I want you to try to replicate:
# 1) Download it (use wget if you don't have curl) curl -O http://valgrind.org/downloads/valgrind-3.6.1.tar.bz2 # use md5sum to make sure it matches the one on the site md5sum valgrind-3.6.1.tar.bz2 # 2) Unpack it. tar -xjvf valgrind-3.6.1.tar.bz2 # cd into the newly created directory cd valgrind-3.6.1 # 3) configure it ./configure # 4) make it make # 5) install it (need root) sudo make install
Follow this, but obviously update it for new Valgrind versions. If it doesn't build then try digging into why as well.
Using Valgrind is easy, you just run valgrind theprogram and it runs your program, then prints out all the errors your program made while it was running. In this exercise we'll break down one of the error outputs and you can get an instant crash course in "Valgrind hell". Then we'll fix the program.
First, here's a purposefully broken version of the ex3.c code for you to build, now called ex4.c. For practice, type it in again:
You'll see it's the same except I've made two classic mistakes:
- I've failed to initialize the height variable.
- I've forgot to give the first printf the age variable.
What You Should See
Now we will build this just like normal, but instead of running it directly, we'll run it with Valgrind (see Source: "Building and running ex4.c with Valgrind"):
$ make ex4 cc -Wall -g ex4.c -o ex4 ex4.c: In function 'main': ex4.c:10: warning: too few arguments for format ex4.c:7: warning: unused variable 'age' ex4.c:11: warning: 'height' is used uninitialized in this function $ valgrind ./ex4 ==3082== Memcheck, a memory error detector ==3082== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al. ==3082== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info ==3082== Command: ./ex4 ==3082== I am -16775432 years old. ==3082== Use of uninitialised value of size 8 ==3082== at 0x4E730EB: _itoa_word (_itoa.c:195) ==3082== by 0x4E743D8: vfprintf (vfprintf.c:1613) ==3082== by 0x4E7E6F9: printf (printf.c:35) ==3082== by 0x40052B: main (ex4.c:11) ==3082== ==3082== Conditional jump or move depends on uninitialised value(s) ==3082== at 0x4E730F5: _itoa_word (_itoa.c:195) ==3082== by 0x4E743D8: vfprintf (vfprintf.c:1613) ==3082== by 0x4E7E6F9: printf (printf.c:35) ==3082== by 0x40052B: main (ex4.c:11) ==3082== ==3082== Conditional jump or move depends on uninitialised value(s) ==3082== at 0x4E7633B: vfprintf (vfprintf.c:1613) ==3082== by 0x4E7E6F9: printf (printf.c:35) ==3082== by 0x40052B: main (ex4.c:11) ==3082== ==3082== Conditional jump or move depends on uninitialised value(s) ==3082== at 0x4E744C6: vfprintf (vfprintf.c:1613) ==3082== by 0x4E7E6F9: printf (printf.c:35) ==3082== by 0x40052B: main (ex4.c:11) ==3082== I am 0 inches tall. ==3082== ==3082== HEAP SUMMARY: ==3082== in use at exit: 0 bytes in 0 blocks ==3082== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==3082== ==3082== All heap blocks were freed -- no leaks are possible ==3082== ==3082== For counts of detected and suppressed errors, rerun with: -v ==3082== Use --track-origins=yes to see where uninitialised values come from ==3082== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 4 from 4) $
If you run valgrind and it says something like by 0x4052112: (below main) (libc-start.c:226) instead of a line number in main.c then add run your valgrind command like this valgrind --track-origins=yes ./ex4 to make it work. For some reason the Debian or Ubuntu version of valgrind does this but not other versions.
This one is huge because Valgrind is telling you exactly where every problem in your program is. Starting at the top here's what you're reading, line by line (line numbers are on the left so you can follow):
- You do the usual make ex4 and that builds it. Make sure the cc command you see is the same and has the -g option or your Valgrind output won't have line numbers.
- Notice that the compiler is also yelling at you about this source file and it warns you that you have "too few arguments for format". That's where you forgot to include the age variable.
- Then you run your program using valgrind ./ex4.
Then Valgrind goes crazy and yells at you for:
- On line main (ex4.c:11) (read as "in the main function in file ex4.c at line 11) you have "Use of uninitialised value of size 8". You find this by looking at the error, then you see what's called a "stack trace" right under that. The line to look at first (ex4.c:11) is the bottom one, and if you don't see what's going wrong then you go up, so you'd try printf.c:35. Typically it's the bottom most line that matters (in this case, on line 18).
- Next error is yet another one on line ex4.c:11 in the main function. Valgrind hates this line. This error says that some kind of if-statement or while-loop happened that was based on an uninitialized variable, in this case height.
- The remaining errors are more of the same because the variable keeps getting used.
- Finally the program exits and Valgrind tells you a summary of how bad your program is.
That is quite a lot of information to take in, but here's how you deal with it:
- Whenever you run your C code and get it working, rerun it under Valgrind to check it.
- For each error that you get, go to the source:line indicated and fix it. You may have to search online for the error message to figure out what it means.
- Once your program is "Valgrind pure" then it should be good, and you have probably learned something about how you write code.
In this exercise I'm not expecting you to fully grasp Valgrind right away, but instead get it installed and learn how to use it real quick so we can apply it to all the later exercises.
- Fix this program using Valgrind and the compiler as your guide.
- Read up on Valgrind on the internet.
- Download other software and build it by hand. Try something you already use but never built for yourself.
- Look at how the Valgrind source files are laid out in the source directory and read its Makefile. Don't worry, none of that makes sense to me either.