How to organize, compile, and debug a C program

This chapter is a list of practical tips about the organization, compilation, debugging, and execution of a C program.  The chapter assumes the GNU/Linux operating system, but something similar is valid for MAC and Windows systems.

Table of contents:

Tips on how organize a C program

Every C program is a set of functions, one of which is called  main.  The execution of the program consists of the execution of main, which typically calls other functions of the set.  In a well-organized program, the function main has a merely managerial role:  it takes care of the input of data, the output of results, and the call of other functions.

The set of functions that constitutes a program resides in one or more files, known as source files.  Each source file is a module of the program.  The name of each module ends with .c.  The root module is the one containing the function main.  In the following examples, we assume that our program has two modules,

ppp.c   and   qqq.c

and ppp.c is the root module.  A good programmer will distribute the functions among the various modules in a logical manner, putting into the same module the functions that are somehow related.

For each module, except the root, it is important to write a header file containing the macros, the declarations of the possible global variables, and the prototypes of the functions which the root module is allowed to call.  The name of the header should match the name of the corresponding module: if the module is qqq.c then the interface is qqq.h.  Assuming that ppp.c is the root module, the complete program will consist of the files

ppp.c ,  qqq.c ,  and   qqq.h .

How to compile a C program

Before your program can be executed, it must be compiled. The compilation will transform the set of source files into an executable, or binary, file.

Preparations.  The first step is simple but important: open a terminal, create a work directory, and go to that directory:

~$ mkdir my-work-dir
~$ cd my-work-dir
~/my-work-dir$

Move all the files of your program to the work directory and check the contents of the directory:

~/my-work-dir$ ls -1
ppp.c
qqq.c
qqq.h
~/my-work-dir$

Compilation.  To compile the set of files of your program — thereby transforming it into an executable file xxx — call the gcc compiler.  For example,

~/my-work-dir$ gcc ppp.c qqq.c -o xxx 
~/my-work-dir$

The first phase of the compilation is a preprocessing that takes care of all the lines of code that begin with a #.  The second phase compiles the preprocessed files.

The compiler displays a list of all the errors found in the source files. Such errors prevent your program from being compiled. Correct the errors with the help of a text editor and compile the program again.

Compiler warnings.  Besides detecting the compilation errors, the gcc compiler may issue warnings about flaws that do not prevent compilation but deserve attention. To ask gcc to issue such warnings, use the  -std=c99  and  -Wall  options on the command line

~/my-work-dir$ gcc -std=c99 -Wall ppp.c qqq.c -o xxx 

The -std=c99 option specifies the C99 standard of C and the -Wall option asks for all the possible warnings. (You may also want to use the options -Wextra, -Wno-unused-result, -Wpedantic, and -O0.)  If your program uses the math library, that is, if it has an #include <math.h> line, you should also add the  -lm  option to the end of the command line.

The compiler warnings should be taken very seriously.  You must correct the causes of the warnings and compile the program again. (Fortunately, you do not have to retype all the options and file names: just use the  key of the keyboard.)

The compile–correct–compile cycle must be repeated until there are no more warnings.  Only after that the program xxx will be ready to run.

How to execute a compiled program

Now that your program was successfull compiled, the executable program contained in file xxx can be executed:

~/my-work-dir$ ./xxx

(The prefix ./ is needed to indicate that xxx is the file in your work directory rather than some namesake xxx in some other directory.)

If the function main of your program has parameters, type the corresponding arguments on the command line. For example, if main has three parameters, type something like

~/my-work-dir$ ./xxx arg1 arg2 arg3

Typically, some of the arguments are names of data files that the program xxx must process. It is a good idea to put all these data files into your work directory before running the program.

Make and Makefile

To automate the compilation of your program, thereby reducing the need to repeatedly type long command lines, use the make utility.  In the case of the example discussed above, put a simple Makefile file in your work directory and say

~/my-work-dir$ make xxx

to compile the program.

Debugging and GDB

The art of debugging is figuring out
what you really told your program to do
rather than what you thought you told it to do.

— Andrew Singer

Debugging is twice as hard
as writing the code in the first place.
Therefore, if you write the code as cleverly as possible,
you are, by definition, not smart enough to debug it.

— Brian W. Kernighan

Suppose that your program has finally passed the compilation hurdle without any warnings.  Unfortunately, it may not be free of bugs.

Run your program on some test data.  The first attempts may end abruptly in a crash, such as a segmentation fault (which is an attemp to access a memory location outside the area reserved for the execution of your program).  To find the error that caused the crash, put your detective hat on: carefully examine the source files of the program and the results of the attempted run.

If you find the error, correct it and recompile the program. If you are unable to find the error manually, recompile the program with the  -g  option of gcc and then use the powerful GDB Debugger.

Memory leaks

A particularly nasty kind of bug is a memory leak:  the program works very well when the amount of data is small but runs out of memory and crashes when the of amount of data is large.  (You may see this happening while the program is running by looking at the graph of the amount of used memory in the system monitor or task manager.)  The memory leak happens typically when the programmer forgets to deallocate the memory allocated by malloc.

The best way to discover the location of the leak is to carefully examine the source files of the program.  If this manual analysis is not sufficient, you can resort to the Valgrind utility.


Questions and answers