Lab 4 Preparation

To prepare for Lab Exercise 3 on the make utility, see the information in the man pages at make(1) ("man 1 make") and the GNU Make Manual ("info make"). Some of the information below is adapted from these sources.

Simply put for our use of it, the make utility automatically determines which pieces of a program need to be recompiled, and issues the commands to recompile them. It does this by examining a file of rules you construct in a file usually called Makefile that describes the relationships among the files in your program, and gives the commands for updating each file.

In a program, the executable file is typically created from the compiler library and your object files (with a .o suffix), which are in turn made by compiling source files (.c). Your include (.h) file (or files -- you may have several) is normally used to show which source file rely upon it.

The make utility works with any compiled language, not just C, and is also used for many other tasks (such as product installs) due to its exceptional flexibility. Once a Makefile exists, type this command each time you change any file:

make

No arguments are usually required to perform all the necessary recompilations, but see make(1) for more than enough options for anyone. The make program uses the makefile rules and the last modification timestamp on the files to decide which of them needs to be updated. For each of those files, it issues the commands as recorded in the makefile.

Normally you should call your makefile either makefile or Makefile (you will often hear the Makefile form recommended because it appears prominently near the beginning of a directory listing, right near other important files such as your README). This of course also implies that you should keep each project (exercise or assignment) in its own directory, with its own backup subdirectory.

Here is a sample Makefile (created by vi and simply stored as a plain text file):

CFLAGS = -ansi -pedantic -Wall -Wextra -O2
LFLAGS = -O2

myProg: main.o partA.o partB.o
=> gcc ${LFLAGS} -o myProg main.o partA.o partB.o
main.o: main.c myProg.h
=> gcc ${CFLAGS} -c main.c
partA.o: partA.c myProg.h
=> gcc ${CFLAGS} -c partA.c
partB.o: partB.c myProg.h
=> gcc ${CFLAGS} -c partB.c

clean:

=> rm -f core* *.o

Note that => represents the TAB character, and is required where shown.

Each section starts with a target filename, and identifies all of the files upon which it depends (the target's dependencies). The first target in the file is special, and directs the make activity. The next lines (plural; you may have several lines to run) contain the commands to run in order to produce the first target file; each must begin with at least one TAB, but there may be several.

The make utility scans the entire list of files, both targets and dependencies, and uses the file dates and times to determine the correct order of execution to produce the first target automatically. You may also have comment lines if they begin with #, and blank lines anywhere for improved readability.

For example, in the above Makefile there are 4 target files. The first target, myProg (or whatever name you want it to have) describes the executable file. It is dependent on the alteration of any of the object (.o) files. The next 3 targets all have the same form, and have the 3 source files and the #include file as their dependencies.

If only partC.c has been changed (i.e., if it has a date/time stamp that is more recent than the target partC.o file), then the commands associated with that target are executed. Since this changes the timestamp for its object file, make will automatically re-create the myProg target. What commands would be executed if only the #include file were to be updated?

The last target, clean, has no dependencies and appears unrelated to the rest of the makefile. It is optional, and must be named to be run, make clean. It will remove all object files when you no longer need them, and any core file (from a memory dump, say for a segmentation fault) if there is one. The -f option (it stands for force) avoids prompts and removes any matching files found.

Variables can be used and set from inside or from the command line as VAR = value, and substituted into the commands as $(VAR) or ${VAR}. It's used here to specify CFLAGS (flags for the compile stage) and LFLAGS (flags for the link stage). This can also be handy for adding g, for example, when debugging. See info make in the Linux Lab for more information.

You might also wonder what sort of thing goes into the common #include file. Say you have the functions main(), funcA(), and funcB(). Let's also say that you're just doing simple I/O. Your #include file would look something like this:

#ifndef H_MYPROG_H
#define H_MYPROG_H

#include <stdio.h>

int funcA(int);
int funcB(int);

#define MAX 128

#endif

The only things that normally go into a #include file are other #includes, function prototypes, macros, extern declarations for global variables (very rarely), pre-processor directives, and widely-used definitions like struct and typedef, but never object declarations or code. In the body of this sample, you can see the #include for stdio.h, two function prototypes (each takes an int as argument and returns an int), and a macro defining MAX as the value 128. It would also have some comments, normally, and blank lines for clarity. It's invoked in each .c file as #include "myProg.h" (note the double quotes instead of angle brackets).

The framing #ifndef, #define, and #endif are pre-processor directives that ensure that this #include file can only be effective once per source (.c) file. You will note that I've used the name of the #include file, myProg.h, to create the name used by the include guard, H_MYPROG_H.

It is standard convention to have macro names all UPPER CASE, to put include guards in #include files, and to use the form H_name_H for the include guard.

Now read over the instructions for Lab 4 and follow them carefully.