2025 March 29,
Graphics programs written in C can be hard to debug. Sometimes they show useful information, but sometimes they just show a blank screen, and sometimes they just crash. Here are a few remarks about debugging, in increasing order of sophistication.
A segmentation fault or seg fault occurs when your program attempts to access memory that is forbidden to it. The operating system detects the illegal memory access and shuts down your program. But why does it happen? In my experience, there are two main causes.
The first cause is array overruns. Suppose that you declare an array with 100 elements, and then you accidentally try to access the 200th element. The C compiler does very few checks for such errors. Even if it did more checks, it is not possible to detect all such errors at compile time. Anyway, if the array overrun causes your program to leave its allocated memory, then you get a seg fault. (If the array overrun does not cause your program to leave its allocated memory, then you don't get a seg fault, but your program probably doesn't work correctly.)
The second cause is dereferencing a bad pointer. For example, if you declare a pointer variable but forget to initialize it, then its value is garbage, and dereferencing the pointer might cause you to leave allocated memory. Also, many functions that return a pointer use the convention that NULL (the address 0) is returned when an error occurs. If you fail to check for null pointers, then you might accidentally attempt to access memory location 0, which is always invalid.
The simplest debugging tactic is adding print statements to your code. To do this well, define a function as follows, near the top of your main.c.
void debug(const char *message) { fprintf(stderr, "%s", message); fflush(stderr); }
If you're debugging a program that crashes, then sprinkle statements like debug("made it to here") throughout your code, and see which ones produce output before the crash. By iterating this process, you can pinpoint the exact line of code that causes the crash.
The key trick here is fflush. It forces the print statement to finish before your program moves on to the next line of code. Without it, your program might crash after the print statement but before the message is actually printed, giving you the wrong idea about where the crash happens.
Even if you're not interested in crashes, you might edit debug to have it print the values of variables that you're interested in. Here's an example:
void debugInt(int integer, const char *message) { fprintf(stderr, "%d %s", integer, message); fflush(stderr); }
A debugger is a program that allows you to inspect your program in memory as it is running. In a sense, it's the print-out-variable-values idea, which I explained above, made more powerful and convenient. See my Debugger Tutorial.