# AppendixCUsing GNU make to Build Programs¶ permalink

This discussion covers the fundamental concepts employed in a Makefile. My intent is to show you how to write a Makefile that helps you to debug your programs. The problem with many discussions of make is that they show how to use many of the “features” of the program. Many problems students have when debugging their programs are actually caused by errors in the Makefile that cause make to use its default behavior.

For example, if you try to compile the program myProg with the command:

make myProg


make will look for its instructions in a file in the current directory named Makefile (or makefile). Because you can only have one file named Makefile in a directory, you need to have a separate directory for each program if you are using the make program.

Assuming Makefile exists, make searches the file for a target (defined later in this chapter) named myProg. If there is no Makefile, make searches for a file named myProg.s, myProg.c, or myProg.cc. If either of the .s or .c source files exists, make issues the command:

cc myProg.c -o myProg


or if the .cc source file exits,

g++ myProg.cc -o myProg


Notice that the compiler is invoked with only the -o option. You will not be able to use gdb to debug the program because the -g option does not get used. This means that if one of the entries in your Makefile is incorrect, the default behavior may cause make to compile a source file without the debugging option.

I know from personal experience that it is easy to not notice that this has occurred. It is much easier to avoid these problems if you:

• Keep your Makefile very simple.

• Read what make writes on the screen very carefully when it executes a Makefile.

A Makefile consists of a series of entries, each of which consists of:

1. One dependency line, which has the format:

target: [prerequisite1] [prerequisite2] ...

Prerequisites are names of files or other targets in this Makefile. Use spaces as separators between the prerequisites, not tabs.

2. Zero or more Linux command lines, each of which has the format:

1. Each line must begin with a tab character (not a group of spaces). Be careful to make sure that your editor does not replace tabs with spaces.

2. The remainder of the line is a Linux command.

There can also be (should be!) comment lines, which begin with a ‘#’ character.

If you invoke \texttt{make} with no argument:

make

it starts with the first entry in the Makefile.

Starting with the prerequisites of the first target—the first entry or the argument used in the command—make follows the hierarchical tree of target/prerequisite entries until it gets to the lowest level—either a file or no prerequisites. It works its way back up through this tree. If any entry along the tree has a target that is not at least as recent as all of its prerequisites, the commands for that entry are executed.

A common organization is to have an entry for the program name, with each of the object files that make up the program as prerequisites. The command(s) in this entry link the object files together. Then there is an entry for each object file, with its source file and any required local header files as prerequisites. The command(s) in each of these entries compile/assemble the source file and produce the corresponding object file. It is also common to have various utility targets in a Makefile.

These concepts are illustrated in Listing C.0.9.

The functions in real programs are distributed amongst many files. When changes are made, it is clearly a waste of time to recompile all the functions. With a properly designed Makefile, make only recompiles files that have been changed. (This is a motivation for placing each function is its own file.) Listing C.0.10 illustrates a Makefile for a program where the main function and one subfunction are written in C and one subfunction is written in assembly language. Notice that header files have been created for both subfunctions to provide prototype statements for the main function, which is written in C. The assembly language source file does not #include its own header file because prototype statements do not apply to assembly language.

As you can see, there is quite a bit of repetition in a Makefile. Variables provide a good way to reduce the chance of typing errors. The Makefile I used for the program in Listing C.0.11 illustrates the use of variables to simplify the Makefile.

Executing the Makefile in Listing C.0.11 gives:

pi@rpi3:~/bookProgs/appendixB/biggerProg $make gcc -g -c -O1 -Wall -o biggerProg.o biggerProg.c gcc -g -c -O1 -Wall -o sub1.o sub1.c as --gstabs -o sub2.o sub2.s gcc -o biggerProg biggerProg.o sub1.o sub2.o  Now let us omit some of the entries from the Makefile in Listing C.0.11 to create the INCOMPLETE!! Makefile in Listing C.0.12. When I execute the INCOMPLETE!! Makefile in Listing C.0.12, I get: pi@rpi3:~/bookProgs/appendixB/biggerProg$make
cc    -c -o biggerProg.o biggerProg.c
cc    -c -o sub1.o sub1.c
as   -o sub2.o sub2.s
gcc -o biggerProg biggerProg.o sub1.o sub2.o


make created the program just fine, but it was created without the debugging options. So I am unable to use gdb to debug my program.