Debug workshop
Written by Chad & Mark
The original version had a major bug in the example code that uses:
#define PRINTF(fmt, args...) printf (fmt, ## args)The original code _may_ have compiled but certainly would not work.
Most debuging is done because of a logic error. The code isn't doing what you think it should be. In order to find and fix a logic error, you normally need to display information about what is happening and in what order. Once that is displayed, it is relatively easy to figure out WHY things aren't going the way you think they should. Debug information could consist of markers like this (pure C):
printf("Stage 1 entered\n"); (more code) printf("End of stage 1.\n");Or, if more information is required, also dump the value of variables. Please note that all integers and characters should always be displayed in hexadecimal. If a character somehow gets a value of 1, in hex it's displayed as 1, as a character it will either look like a smily face, or else be unprintable.
Now, you probably won't want to display debug statements all of the time, that's where this next example comes in. We found this strategy being used by Alessandro Rubini.
#include <stdio.h> #define DEBUG_ON /* remove the previous line to turn off debuging */ #undef PRINTF #ifdef DEBUG_ON #define PRINTF(fmt, args...) printf (fmt, ## args) #else #define PRINTF(fmt, args...) /* do nothing */ #endif // #define DEBUG_ON int main() { int i = 2, j = 8; PRINTF("\ni = %d", i); /* The previous line will only be displayed if we put * #define DEBUG_ON * at the top of this file */ printf("\n\nj = %d", j); /* The previous line will always be displayed. */ printf("\n"); return(0); }
In this case, by changing only one line you can tell the compiler to include or remove all of the debug statements.
Richard W. Stevens was the first programmer I've read that used function wrappers in order to debug. Below is an example with the imaginary command "blob". blob returns a positive value on success, and zero on failure. If you wanted to assume that the function was always successful, and die with an error message otherwise, you could use this code:
int Blob(int parameters) { int value=0; value=blob(parameters); if (value==0) { printf("The blob command has failed. Ending program.\n"); exit (0); } else return value; }
Now in place of using "blob", you can use "Blob" and it will trap all of the errors. If we were calling "blob" alot, in order to do error handling, it would require 7 lines of additional code. With the "Blob" function, we can stay nice and lazy. "Blob" also allows for cleaner looking code, especially if the function and prototype are kept in a separate file than our main code. This is one of the best reasons to use a Makefile.
Let's say that instead of dying when you hit an error, you needed to recover. It may be possible (depending on the requirments) to use a system similar to Stevens' style. If the parameter was based on data obtained from the user, in Blob we could add a loop to ask for a different parameter and try again until successful. Sometimes it is simply not possible to have elegant error trapping, or else it is not worth the time investment. In a choice between handling errors, reporting errors and dying, or doing nothing, you should always at the very least report errors and die. It is foolish to think that errors simply won't happen, and it may be arrogant to ignore them. Anyway it goes, errors will normally have to be handled differently based on the context in which they are created. There is no single fool-proof error handling code. Sorry.
http://www.csd.uwo.ca/~jamie/.Refs/misc_debug.html
http://www.amp.york.ac.uk/external/visual/jar11/teaching/tbugs.html