Makefiles and multifile programs in C [King, Ch. 15] ------------------------------------- * When a program gets large (i.e., it has many functions), it helps to organize it by splitting it into many source files such that related functions are put in the same file. How do we structure such a program? First, put all the functions of the program into related groups. Each group will correspond to one file (e.g., in A2, we have four groups: random-related functions, event-related functions, queue-related functions, and the main part of the program). Each "group" of functions will consist of some functions that are used in the rest of the program, and some "internal" functions useful only to the other functions inside the group (this should sound familiar: it's a lot like the distinction between public and private methods of a class). For each source file, there should also be a corresponding header file (ending with ".h") that contains the declarations of data types and functions that are meant to be used in the rest of the program (this is what we've done in A2). These header files can then be included where they are needed to make sure the functions are properly declared before being used. Doing things this way makes it easy to change the function later: we only need to change the source file and the header file, without worrying about all the places where the function is used. * How do we compile a program consisting of many files? For example, in A2, we have "random.c", "queue.c", "event.c", and "main.c" that should all be combined somehow into one executable file... It turns out that gcc has options that allow us to do this, for instance: % gcc -c random.c will compile "random.c" but only create an object file "random.o" from it (it won't link it with system libraries to create an executable). So, we could compile the entire program like this: % gcc -c random.c % gcc -c queue.c % gcc -c event.c % gcc -c main.c at which point we have four object files "random.o", "queue.o", "event.o", and "main.o". But how do we create one executable file from these three object files? Simply call the compiler again to link all the files together (and with the standard library functions), like this: % gcc -o sim main.o event.o queue.o random.o * Sounds simple enough, but what if you have a program that consists of 50 different source files? Every time you make a change, you have to remember which files have changed so that you can recompile them into an object file and link the object files together into an executable... Not a fun thing to do by hand! * Luckily for us, there is a nifty UNIX program that can take care of all these details automatically: it's called `make'. `make' works by reading information from a "makefile" that tells it something about the structure of the program (i.e., which files depend on which others), and how to build the executable. Makefiles can be quite complex, but the basic building blocks are: - `macro' definitions of the form NAME = definition allow us to define "variables" and give them a value. The value of such a "variable" can later be accessed as $(NAME). - `dependencies' have the form file: file1 file2 ... and indicate that the first file "depends on" file1, file2, ... in the sense that if file1 is changed or file2 is changed or ..., or if "file" doesn't exist, then "file" needs to be compiled. "file" is called the "target" of the dependency. - `rules' have the form command-line1 command-line2 ... where literaly means a TAB character, and the command-lines specify how a particular file is to be compiled. * For example, let's look at the Makefile for A2: CC = gcc CFLAGS = -Wall -ansi -pedantic LDFLAGS = -lm DEBUGFLAGS = -g CFLAGS += $(DEBUGFLAGS) OBJS = main.o event.o queue.o random.o TARGET = sim default: $(TARGET) $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(LDFLAGS) clean: rm -f a.out core *.o $(TARGET) main.o: main.c event.h queue.h types.h random.h event.o: event.c event.h queue.h random.h queue.o: queue.c queue.h random.o: random.c random.h The first seven lines are macro definitions. Some of these macros are "standard" macros, e.g., "CC" specifies the name of the compiler, "CFLAGS" specifies the command-line options to give to the compiler, and "LDFLAGS" specifies linker options (for non-standard libraries). Following the macros are dependencies and rules. The first dependency tells `make' which file to make by default, i.e., if you type just ``make'', the Makefile specifies that the default target is "sim" (in this case). The second dependency tells `make' that the file "sim" depends on the object files defined in the macro "OBJS", so that if any of the object files change, or if the file named "sim" doesn't exist, "sim" will be recompiled. The next line is a rule that tells make how to compile "sim" from the object files. The next two lines deserve special mention: the name "clean" isn't actually the name of a file, and there is no rule in the Makefile that will ever create a file named "clean", so when you type ``make clean'', the following rule will always be executed: it's designed to remove all backup and object files from the directory, leaving only the source and header files. The four last lines simply list dependencies for the four source files in the program, without any rule: how does `make' know how to compile these files? It uses a set of "implicit" rules for source files that end in ".c": compile using ``$(CC) $(CFLAGS) -c'', which we've seen above will do the right thing.