The make utility is a very flexible tool for, installing downloaded programs and compiling your own code, among other things. Here are some very crude instructions for how to use make to streamline the compilation of your code into executable files.
gcc prog.c -o progBut suppose your code isn't self-contained, that the functions called by main() (in prog.c) reside in a bunch of other files, fndef1.c, fndef2.c, etc. Furthermore, suppose that for each of these files there are corresponding header files (e.g., fndef1.h) containing the function prototypes. Then you might compile using
gcc prog.c fndef1.c fndef2.c fndef3.c ... -o prog -lm(The -lm says link with the standard C math library.) But this can take a long time. The compiler first converts each of the .c files to object code, stored in files prog.o, fndef1.o, fndef2.o, etc. Then it links the object code together into on executable file prog.
Its no problem that it takes a little while to compile, but what if your code is broken somehow, i.e., "in the development stage", and you have to recompile several (read: many many) times? Then you could break the compilation process up. If you are working on prog.c and are certain the other files have no mistakes, then create their object files once an for all with
gcc -c fndef1.c fndef2.c fndef3.c ...(The -c means "just compile, don't link together.") Then you could just type
gcc prog.c fndef1.o fndef2.o fndef3.o ... -o prog -lmevery time you modify prog.c. But what if the problem were not in that program, but in one of the other files? Or worse, their associated header files? Which ones need to be recompiled?
# This is a makefile.
Some quick comments on the above Makefile:
The stuff to the right of a hash (#) is a comment. The make-specific
variables (the ones in capital letters) follow a name convention such that:
CC refers to the compiler (gcc in this case);
CFLAGS contains compiler directives/options; and
LDFLAGS is a list of link (or "load") directives
(here, instructions to link with the C math library).
NB: the blank space in front of the ${CC} ... is a
TAB character. Make will whine if it doesn't find one there.
To create your executable code, type
The magic of make is that it checks the modification
times/dates of the executable file, the object files myprog.o
and more_code.o (if they exist), and the source code
myprog.c and more_code.c. The object files--compiled
versions of the source code--are recompiled if the source code has
been modified since the last compilation. Make knows how
to do this "automatically". Then, if necessary, it (re)links the
object code together to make the executable program.
For example, if you've never compiled any of your code, make
will first compile myprog.c using gcc with the -c
option to spit out the object code myprog.o, then similarly for
more_code.c. Finally it will link the two .o files
together (using gcc with the .o files as arguments but
without the -c directive) to create the executable file.
If you run make and subsequently edit myprog.c, a second
make command will recompile only the source code
myprog.c creating a new myprog.o file; then make
will relink both .o files to generate new executable code.
If you run make twice without making changes to any files, it
will not do anything except inform you that your file prog is
up-to-date.
Thus, make will exert the least amount of effort
(minimimal compile/link time) necessary to keep your executable
code up to date.
Before executing the command list, make checks to see if any of
the dependencies are also targets themselves, specified elsewhere in
the makefile. If so, it will look at the dependencies of these
targets, then check to see if these new dependencies are also targets,
and so forth in a recursive fashion. It will stop when there are no
more dependencies to check (i.e., no more dependencies which are
themselves targets). By this time, it might have ended up on a target
which is deeply nested in some sense, the N^th level
dependency-of-a-dependency of our original target. If
there is a conflict with this level-N target and its dependencies
(here "conflict" means that the target is file older than the
dependency, or that the dependency and target names don't both resolve
to existing files), then the corresponding commmand list gets
executed, followed by the level-(N-1) command list, all the way down
to the original target's command list. If no conflict
exists, then the utility ignores the list and proceeds to look for a
conflict between the (N-1)^th level target and its dependencies, and
so forth.
The expectation in all this is that by executing command lists,
make will update files (by recompiling code, for example)
so that next time it is called, there will be fewer conflicts
and hence fewer command lists to execute.
This description of recursive dependencies, if it made sense at all,
might not seem to explain the behavior of the example makefile above,
the one with the target named prog. Why would make bother
compiling source code to make object files? The answer is that
make has implicit rules regarding the relationship between
.o files and .c files. We could rewrite the
makefile to make these rules explicit:
Note also the effect of make clean. This chooses clean
as the target; it's (vacant) dependencies do not resolve to an existing
file, so the \rm command gets executed every time.
We have made a target out of both object file names that depend on the
header file. This is because we want both object files to be updated
whenever the header gets changed. If only one of these files had any
reference to the header, say fndef.c, we would have
used
Finally, note that we have taken advantage of the implicit rules about
compiling .c files to make .o files as needed.
That's why we didn't have to put individual targets like
On the other hand we may wish to override the implicit rules.
One case would be if the code in fndef.c needed special
compilation directives or if it were instead a Fortran 90 code
fndef.f90. Then the appropriate target might be
To wrap up the loose ends like the default value of the global variable,
we use a header file, namely the one mentioned in the second line of
the code above, fndef.h. Its contents might be
Of course you can't believe everything you read on the web.
IMPORTANT! The blank space preceding each command (non-blank line[s]
immediately below a target name) in a makefile must be a TAB
character. If you just cut and paste a makefile off of the web, you
will need to replace the blank spaces with a TAB (using emacs, for
example).
bcb 5-Oct-98.
The basics
The solution is to use the make utility. To begin with let's
assume that we have only two files to compile and link together,
myprog.c (where main() is defined) and
more_code.c (where some functions called by main are defined)
neither of which use any header files except the standard ones (e.g.,
stdio.h). To use make, a third file called
Makefile should be created with the following text:
CC = gcc
CFLAGS = -Wall
LDFLAGS = -lm
myprog: myprog.o more_code.o
${CC} ${CFLAGS} myprog.o more_code.o ${LDFLAGS} -o myprog
clean:
\rm myprog.o more_code.o myprog
make myprog
(actually, just typing make will do the trick, for reasons explained
below). This will generate an executable file myprog.
To remove your files created with make, type
make clean
Some details.
The above example demonstrates what make does. It is important
to examine why it does it. First we need some vocabulary (not all of
which is gauranteed to be standard). A generic make file might look
like this:
vars = values
When invoked as a shell command, make opens up Makefile
(hereafter "the makefile") and seeks out the target name which matches
its command-line argument, or the first target name if no arguments
were given. Lets suppose the command was make target;
then make would "go" to the line containing
target: and look at the dependencies, typically a list
of files. If any of those files were modified more recently than a
file with the name target, the utility prepares to
execute the command(s) listed in the line(s) immediately below the
target-dependencies line. This happens also if no file named
target exists, or if any of the dependencies correspond
to non-existent filenames.
target: dependencies
command-list
targ2 : more-depencies
another-command-list
.
.
.
Now it is explicit: myprog depends on myprog.o and
more_code.o, these in turn depend on the respective .c
files, and everytime there is a modification time/date conflict,
the appropriate object file gets recompiled and the executable
code gets updated.
CC = gcc
CFLAGS = -Wall
LDFLAGS = -lm
myprog: myprog.o more_code.o
${CC} ${CFLAGS} myprog.o more_code.o ${LDFLAGS} -o myprog
myprog.o: myprog.c
${CC} ${CFLAGS} -c myprog.c
more_code.o: more_code.c
${CC} ${CFLAGS} -c more_code.c
clean:
\rm myprog.o more_code.o myprog
Example src-code/header-file pair
As another example, let's consider a case where we have a local, user-created
header file, in addition to C source-code. Let's call the relevant files
prog, fndef.c and fndef.h, supposing that
the first is where main() is defined and the last two contain
a function definition and a function prototype, respectively.
A reasonable makefile would be
CC = gcc
Note that we have defined a variable objects to contain
the names of the object files. We may just as well have typed them out
explicitly, but somehow, fewer keystrokes make for better organization.
CFLAGS = -Wall
LDFLAGS = -lm
objects = prog.o more_code.o
prog: $(objects)
${CC} ${CFLAGS} ${objects} ${LDFLAGS} -o myprog
${objects}: fndef.h
fndef.o: fndef.h
in the last line above. If other "local" header files were included,
then we would have listed them as dependencies also.
fndef.o: fndef.h
${CC} ${CFLAGS} -c fndef.c
where f90 is the Fortran 90 compiler.
fndef.o: fndef.h fndef.f90
f90 -c fndef.f90
Using header files -- a digression
Why would you want to complicate things by introducing a header file?
To answer this question, suppose the file fndef.c is composed
as follows:
#include <math.h>
Note a couple of things: the integer variable fndef_parameter
lies outside the scope of the function fndef and can be
accessed/changed in other parts of a program which is linked with this
code. Presumably, it will affect the execution of the function, but we
hope that it's effect is arcane, otherwise it really should be in the
argument list. Its default value is evidently going to be specified
by a symbolic constant.
#include "fndef.h"
int fndef_parameter = FNDEF_PARM_DEFAULT;
float fndef(float *x, int n)
{
...
}
#define FNDEF_PARM_DEFAULT 10
/* a parameter's default value */
That would do it. When you compile fndef.c, the header is
inserted in at the top, providing both the default value for the
global parameter and a function prototype as a cross-check.
Whenever you want to use this function in a program, just
#include the header file, as in this example:
#define FNDEF_PARM_ALTERNATIVE 99
/* another good choice? */
extern int fndef_parameter;
/* the parameter use by fndef() stored as a global variable */
float fndef(float *x, int n);
/* function prototype */
#include <stdio.h>
Notice that the header file already takes care of the
function prototype and the declaration of the external
variable.
#include <math.h>
#include "fndef.h"
...
int main()
{
...
fndef_parameter = 1234;
zz = fndef(q,npoints);
...
}
Hints
Fire up your browser and go to a search engine like this one and search for keywords
such as make, CFLAGS, AND LDFLAGS. You may find
a wide variety of makefiles, some with very sophisticated usage which
you may want to adopt.
Return to the top of this page.