Dependencies:

In the example in lesson 1, we made a program called myprog from two source files: mainfile.c and math.c. math.c contains the code for a set of math functions which we referenced in mainfile.c. Suppose we have now found a bug in our code for the function add_em_up(). If we change the code in math.c, we need to recompile the object math.o and the executable myprog in order for the bug to be fixed in our program. We do not have to recompile mainfile.c to make mainfile.o again, because mainfile.o only contains references to the functions in math.o, not the functions themselves. We only need to relink the two objects into an executable after fixing math.o in order to propagate the bug fix to myprog.

Recall (from lesson 1) that myprog must be built from mainfile.o and math.o, that mainfile.o is built from mainfile.c, and that math.o is built from math.c. We say that myprog "depends" on mainfile.o and math.o, that mainfile.o "depends" on mainfile.c, and that math.o "depends" on math.c.

We can show this set of relationships in graph form:

           ---> mainfile.o ---> mainfile.c
          /
myprog ---
          \
           ---> math.o ---> math.c
In this graph, the arrows (Okay, so it isn't artwork. Tough.) show the dependencies between the files. Changes must be propagated against the direction of the arrows. Thus, we see that a change in mainfile.c will cause mainfile.o to be "out-of-date", which will in turn cause myprog to be out-of-date. We can also see that this change will not affect math.o or math.c. Similarly, a change in math.c will cause math.o and myprog to be out-of-date, but not affect mainfile.o or mainfile.c.

This shows that splitting the program into two files not only allows us to easily reuse the math object in other projects, but it saves us from a complete recompile of the entire project if only one function is changed.

Makefile representation of a target and its dependencies:

Above, we showed that using multiple files in a project can be beneficial, but it can also be a pain in the neck. Keeping track of which object was built when, and wether or not a particular executable depends on something that was just changed can be a real hassle. It is for this reason that make was created. make reads a file called a "makefile" (usually named makefile or Makefile) which lists the project's executables and/or objects along with the files upon which they depend. It also contains lists of commands to execute in order to rebuild dependant files from their dependencies.

Let's look at the part of the makefile for myprog above:


myprog: mainfile.o math.o
	cc -o myprog mainfile.o math.o

This is called a rule. For the moment, we won't worry about the rest of the makefile. The first part is the word myprog followed by a colon. This defines the target. The target is the file we want built in some manner (for our purposes, by compiling other files). The rest of the rule describes the target's dependencies and the commands for building the target from these dependencies.

After the colon, we see mainfile.o and math.o These are the files on which myprog depends. When make is told to build myprog it will check to see if these files are up to date (more on that later). If they are, and myprog is newer than these files, make will assume that myprog doesn't need to be rebuilt. Otherwise, it will make sure that each of these files is up-to-date by finding the rules which define their dependencies, etc.

Once all the dependencies have been brought up to date, make will execute the command on the second line of the rule to update myprog The second line must begin with a tab character (no spaces!), and contains a command that could be typed at the shell prompt to make the target from the dependencies. In this rule, the command is "cc -o myprog mainfile.o math.o". This is the command we saw above which links the object into an executable file.

Suppose mainfile.o doesn't exist when we tell make to build myprog, then the target for mainfile.o is found, its dependencies are checked and rebuilt if necessary, and the commands to build mainfile.o are executed. To build mainfile.o we need to compile mainfile.c into an object:


mainfile.o: mainfile.c
	cc -c -o mainfile.o mainfile.c

We see that this just encodes the dependency of mainfile.o on mainfile.c, and that you would use the command "cc -c -o mainfile.o mainfile.c" to build mainfile.o from mainfile.c.

Similarly, for math.o, we would have the rule:


math.o: math.c
	cc -c -o math.o math.c

Putting these rules all together, we have the makefile:

myprog: mainfile.o math.o
	cc -o myprog mainfile.o math.o

mainfile.o: mainfile.c
	cc -c -o mainfile.o mainfile.c

math.o: math.c
	cc -c -o math.o math.c

Let's look at the graph we made earlier showing the dependencies of the files:
           ---> mainfile.o ---> mainfile.c
          /
myprog ---
          \
           ---> math.o ---> math.c
If we type "make myprog" at the command prompt, make will walk the graph we showed above to find all the dependencies that have be changed, then walk back (against the arrows) updating targets until it rebuilds myprog.
This page was last modified .