
The ECN No Name Newsletter is no longer being published. This is an archived issue.
[previous article] [next article]If you write and compile large programs on the UNIX8r9 system, it won't be long before you notice the need to keep your compilation time down. Generally it is not a good idea to keep all of your code in one huge file. Not only is it unnecessary to recompile the good code continually along with the bad; it is also wasteful and slow! An easier and quicker method is to break your program down into a number of files, each of which contains several routines that perform related actions, thus modularizing your code and speeding things up considerably.
This is where make comes in. make is a UNIX utility program that's easy to use. It keeps track of your program files (both source and .o files) and recompiles only the files that are not up to date. By using make you'll be saved from doing "ls -l" all the time to see whether your source file is newer than the object code or executable program. Also, you'll be able to run your program without having to endure long compilation times. To see the difference this utility can "make" for you, let's look at and compile a small C program contained in 4 tiny files.
$ cat header.h
#define COUNT 17
#include
float Avg();
$ cat main.c
#include "header.h"
main()
{ int sum;
float average;
sum = Add(COUNT);
average = Avg(sum, COUNT);
PrintResults(sum,COUNT,average);
}
$ cat printfuncts.c
PrintResults(sum, count, average)
int sum, count;
float average;
{
printf("Sum of the numbers ");
printf("1 through %d: %d\n",
count, sum);
printf("Their average is: );
printf("%.2f\n", average);
}
$ cat mathfuncts.c
Add(count)
int count;
{
int i, total = 0;
for (i = 1; i <= count; i++)
total = total + i;
return(total);
}
float Avg(sum, count)
int sum, count;
{
float average;
average = sum / count;
return(average);
}
Normally, if we were to get this program ready to run, we would need to issue a number of compiler commands, one for each of the source files. Then we would issue another cc command to link the .o files together. However, if we use make, these commands are automatically executed: $ make cc -c main.c cc -g -c printfuncts.c cc -g -c mathfuncts.c cc -g main.o printfuncts.o mathfuncts.o
Now, let's run our little program: $ a.out Sum of the numbers 1 through 17 is: 153 Their average is: 9.00 Let's change our definition of COUNT to, let's say, 15. We go into the file header.h and make the change, but when it's time to recompile our program, which files do we redo? make knows which files are dependent on that header file, so when we run make, only those dependent files are redone. $ make
cc -c main.c
cc -g main.o printfuncts.o mathfuncts.o
Notice that make did not bother recompiling printfuncts.c or mathfuncts.c. This would save a lot of time, had this been a large program.
OK...how did make know how to issue the above commands? Well, make looks for a file in your directory called either makefile or Makefile (in that order). First, we'll take a look at the contents of our Makefile then show you how to set up your own. $ cat Makefile # # Maintain the following definitions: # # HDR---all header files (*.h) # SRC---all C source files (*.c) # OBJ---all object files (*.o) # # Use the following make targets: # # all-----(or nothing) to build a.out # lint----to check your program with lint # clean---to blow away all .o files HDR = header.h SRC = main.c mathfuncts.c printfuncts.c OBJ = main.o printfuncts.o mathfuncts.o DEP = ${SRC} ${SRCg} CFLAGS = -g db: ${OBJ} cc ${CFLAGS} ${OBJ} lint: lint -hxn ${DEP} clean: rm -f ${HDRg} ${SRCg} ${OBJ} main.o: main.c header.h cc -c main.c
For ease of reading and changing things, files are grouped together in a Makefile according to purpose. Notice, for example, that all source files (those ending in .c, .f, etc.) are grouped together under the make variable SRC, object files under OBJ, and header files under HDR. You can come up with your own "make variables"; these variable names are simply a convention, but it's very important that for every file in the SRC variable, you have a corresponding .o file in the OBJ variable. Accessing them is just as easy. The format is simply ${variable}...for example ${SRC}.
Whenever make is run, a "target" is specified, and the corresponding line from the Makefile is executed. In our example, if we were to type "make clean" , all object files would be removed from our directory. Typing "make lint" would run lint on all of our source files, while typing "make" command (no "target" needed) just compiles all source files and links them with all existing object files. (If the target is not specified, the first target is automatically chosen.)
Notice also that flags can likewise be grouped under variables. In our case the CFLAGS variable contains only the -g option for debugging. Even though we have just a single flag, it's best to put it in a variable in case we want to add more compiler flags later. The last line of our Makefile is called a dependency. Remember when we changed the value of COUNT in our header file? Well, this line tells make that our main.o file depends on not only the corresponding source file, main.c, but also on header.h. This is important for us to include in our Makefile, since whenever we make any changes in the header.h file, our main.c will also be recompiled, with all changes being kept up-to-date.
One important thing to remember when building your Makefile is that each target or dependency line is followed by the action to be taken, and that each of these action lines MUST begin with a tab. Notice in our example, that the line following db: ${OBJ} is indented with a tab.
The best way to learn how make works is to play around with a simplified program like the one given above, make changes in your Makefile and watch what make does. For other examples and more in-depth information on make see the UNIX Programmer's Supplementary Documents, Volume 1, Section 12 (PS1:12). This documentation is available for sale at the Armory and can be checked out from the Potter Library.