Let's look at a program that adjusts grades.
There are some parts of the program that are generic and could be separated out for possible reused.
What are the parts that could be separated out?
When we separate a set of code into a self-contained entity, we call this a module.
To create a C module for the grade functions, we need to
create 2 new source code files: a
.h file and a
.c file. It is typical for the base name of those files
to be the same, and to relate to the functionality of the module.
Thus, we'll name them:
|Contains stuff people calling the grade functions need to know.||Contains stuff people writing the grade functions need to know.|
Since this module only contains the grade functions, we also need a
file for the main program--let's call it
We've already broken our original program up into these files and made them available for you to download. There is also a copy of a data file (for testing) and a make file that we'll use shortly.
Let's look at each file and discuss them in more detail...
How does the implementation file
grades.c get prototypes
One thing is missing from our new grades module...The header should be wrapped to prevent multiple inclusion.
Header files, like our header for the grades module (
include things like: constants, new types, and functional prototypes.
This code should only be included into a
once; otherwise, the compiler may complain and refuse to compile the
You may think that including the code from a header twice would be unlikely, in that we would rarely mistakenly do the following:
#include "grades.h" ... #include "grades.h"
However, multiple inclusion is often likely in the following form:
#include "grades.h" ... #include "final.h"
In this case, you might get some compiler error like:
In file included from somefile.c:17: final.h:12: redefinition of `something' final.h:12: `something' previously declared here
I.e., it did not like the fact that the same thing was defined twice.
To solve the problem of multiple inclusion, we wrap the header by doing the following... Put the whole header inside an if-not-defined construct, like:
where#ifndef GRADES_H #define GRADES_H ... CONTENTS OF grades.h ... #endif /* not defined GRADES_H */
GRADES_His unique to the file
GRADES_H) is to take the file name of the header (e.g.,
This usually gives a unique symbol.
Being wrapped, the first time
grades.h is included:
the symbol#include "grades.h" /* GRADES_H not defined, but once this */ /* include is done it will be. */
GRADES_Hwill not be defined and the code in the header will be compiled. Later, if the header is included again (directly or indirectly):
in the same source file, then the code will be skipped since... #include "final.h" /* This tries to include grades.h. */
GRADES_His already defined (i.e., it got defined the first time we included
Go ahead and wrap
grades.h (i.e., edit
Now we would like to compile and link our program, which is made up of
multiple files (header (
.h) and source (
We can use
gcc as usual, and just need to specify the
source code files that make up the program...
gcc -ansi -pedantic -Wall -o adjust adjust.c grades.c
Compiling produces a problem:
/var/tmp/ccy8x5x_1.o: In function `main': /var/tmp/ccy8x5x_1.o(.text+0x1e0): undefined reference to `PrintGrades' /var/tmp/ccy8x5x_1.o(.text+0x21c): undefined reference to `PrintGrades'
It is complaining that there is something called
main()) that is not defined. What kind of
error is this?
Fix the error by correcting the function name in
gcc -ansi -pedantic -Wall -o adjust adjust.c grades.c
We now have an executable
adjust that can be run on the
adjust < data1
It should run fine.
Notice that after we made a change to the source code in
adjust.c, we ended up recompiling everything with...
gcc -ansi -pedantic -Wall -o adjust adjust.c grades.c
This is wasteful because nothing in
grades.c, so why should
What we should have been doing instead is compiling source code to intermediate object files and then linking them together.
First, recall that the program is made up of 3 source files:
adjust.c, a main program.
grades.h, interface for the grades module.
grades.c, implementation file for the grades module.
We want to compile each piece of the program separately and then link them together. This will allow us to only regenerate the parts of the program that are affected when we change some source code.
When we just compile source code (without linking it
together), it means that we take the
.c files and generate
intermediate object (
To just compile source code, use the
with the compiler...
gcc -ansi -pedantic -Wall -c adjust.c gcc -ansi -pedantic -Wall -c grades.c
This will generate the object files
grades.c). List your files to see the intermediate
Finally, to link the object files (
.o) into an executable
that we can run, we use the compiler again (although this time it will
just pass the
.o files on to the linking stage):
gcc -ansi -pedantic -Wall -o adjust adjust.o grades.o
Remember, we use of the
-o flags to tell it to call
adjust instead of
Since we have just compiled the executable
separately on the commandline, we know exactly what commands
are needed to generate the pieces that make up the executable.
We would like to know under what circumstances we'll have to
regenerate each of those pieces. For example, if any of the object
.o) files get changed, then they have to be relinked into
a new executable. Nonetheless, that's not the only case.
Let's start from our goal (i.e., to have an executable named
adjust) and draw a dependency chart...
The executable adjust is made up of the 2 object files, adjust.o and grades.o. Thus, adjust depends on those 2 files. To generate adjust from those files, we link them together.
adjust / \ / link \ / \ adjust.o grades.o
Next, adjust.o depends on adjust.c and grades.o
depends on grades.c. You generate these object files by
compiling the corresponding
adjust / \ / link \ / \ adjust.o grades.o | | | compile | | | adjust.c grades.c
Finally, the 2 source code files depend on grades.h since they
both include that file. This, however, is a slightly different
dependency since the
.c don't need to be generated
.o files and the executable.
adjust / \ / link \ / \ adjust.o grades.o | | | compile | | | adjust.c grades.c \ / \ include / \ / grades.h
adjust. The executable depends on some things directly (like the
.ofiles) and some things indirectly (i.e., you need the
.cfiles to make the
.ofiles to make the executable).
Here is the complete dependency chart...
DEPENDENCY CHART FOR adjust EXECUTABLE adjust / \ / link \ / \ adjust.o grades.o | | | compile | | | adjust.c grades.c \ / \ include / \ / grades.h Dependencies go downward. How to "get" or generate files goes upward.
With such a dependency chart, we can answer questions about what we need to regenerate if we change certain files.
For example, suppose we change the file
What has to be regenerated?
Answer: First, recompile
adjust.c to get
adjust.o. Then, relink all the object files together
again to get the executable
Now, suppose we changed
.cfile changes or a header file included by a
.cfile changes, we have to regenerate the corresponding
Since it is tedious to recompile pieces of a program when something changes, people often use the make utility instead.
Make needs a make file that encodes both the dependencies between files and the commands needed to generate files.
When you run the make utility, it examines the modification times of files and determines what needs to be regenerated. Files that are older than the files they depend on must be regenerated. Regenerating one file may cause others to become old, so that several files end up being regenerated.
A make file may either be named Makefile (first letter uppercase) or makefile (all lowercase). We will prefer the uppercase form, Makefile.
It makes things much easier if the make file is in the same directory as the source code files. You can create a make file in any text editor.
Let's look at the make
file for the executable
For simple make files, there will be at least 2 parts:
Keep in mind that make files use their own language, so DON'T expect things in them to look like C.
Make files should have comments so that they are more readable. In make
files, any line starting with a pound sign (
#) is a
# Makefile for executable adjust
CC = gcc
In general, the form for setting a variable is:
VARNAME = value
(The space around the
= is optional for our version of make,
but adds to readability).
The second variable says what flags to pass to the C compiler...
CFLAGS = -ansi -pedantic -Wall -g
Here, the variable includes the flag that adds debugging information.
The 2 variables,
CFLAGS, are the ones
that our version of make expects to be set for the C compiler
and flags to the C compiler, respectively. You can also use your own
variables, but make has a set of standard variables, like
these, that it uses.
In the next section,
CFLAGS are used
in commands needed to compile/link certain files.
CCto figure out which compiler to use. We ignore these automatic features and write these commands ourselves to help in learning make.
For each target, there are typically 1 or 2 lines in a make file. Those lines specify:
By default, the first target in the make file is the one that gets
generated when you just say "
make" at the commandline, so
it should be the name of the executable, in our case,
For a target's dependency line, the target file name should be listed,
followed by a colon (
:) and the files it depends on:
adjust: adjust.o grades.o
For a target that is an executable, we just list the object files it depends on.
On the next line, there should be a command that generates the target:
<Tab>$(CC) $(CFLAGS) -o adjust adjust.o grades.o
<Tab>(i.e., hit the Tab key)...it cannot start with a regular space!
Note that the variables
used, which means that the command is really:
gcc -ansi -pedantic -Wall -g -o adjust adjust.o grades.o
$) and then the variable name in parentheses.
Our next target is the object file,
For a target that is an object file, we list all files it depends on.
This means its corresponding
.c file, plus any header
.c file includes (i.e., include directly or
indirectly, but not system header files like
aren't going to change).
So, the dependencies line looks like:
adjust.o: adjust.c grades.h
The command to generate this target is:
<Tab>$(CC) $(CFLAGS) -c adjust.c
Finally, the last object file,
grades.o: grades.c grades.h <Tab>$(CC) $(CFLAGS) -c grades.c
That's all for the make file!!! It just encodes the dependencies and the generation commands for 1 executable and its 2 object files. The complete make file looks like:
# Makefile for executable adjust # ***************************************************** # Parameters to control Makefile operation CC = gcc CFLAGS = -ansi -pedantic -Wall -g # **************************************************** # Entries to bring the executable up to date adjust: adjust.o grades.o <Tab>$(CC) $(CFLAGS) -o adjust adjust.o grades.o adjust.o: adjust.c grades.h <Tab>$(CC) $(CFLAGS) -c adjust.c grades.o: grades.c grades.h <Tab>$(CC) $(CFLAGS) -c grades.c
\(backslash, not the forward slash) to tell make that the line continues. So, we could have written the last target as:
grades.o: grades.c \ grades.h <Tab>$(CC) \ $(CFLAGS) -c \ grades.c
Only break lines where space would normally go.
Also, we have done some extra work. Make already knows that a
.o can depend on a corresponding
(among other things). It also knows how to compile a
file into a
.o (assuming the variables
CFLAGS have been set to the values you want--else it uses
the default compiler). Make does not necessarily know which
header files a
.c file includes, however, so we needed to
include that information ourselves. Therefore, the last target could
have just been:
without any generation command needed after it.grades.o: grades.h
The reason we included the extra pieces of information is so that you learn how to write complete dependency and generation lines for targets.
Now that we have a make file that encodes the dependencies between the files that make up the executable, it is easy to compile that executable.
Try using the make file we've given you. First, remove the old executable and object files:
Then, run make with the command:% rm adjust *.o
Remember that this will cause make to generate the first target
in the make file, which is
Since nothing has been compiled/linked, it will do:
gcc -ansi -pedantic -Wall -g -c adjust.c gcc -ansi -pedantic -Wall -g -c grades.c gcc -ansi -pedantic -Wall -g -o adjust adjust.o grades.o
I.e., compile the 2
.c files into object files, and then
link the object files into an executable named
(If you have trouble, make sure that the make file is named Makefile and is in the same directory as the source code files.)
Now, type the make command again (let's explicitly tell it what target to generate this time) and you get:
% make adjust make: `adjust' is up to date.
Here, make tells you nothing has changed, so it has no work to do.
Next, suppose we want the program to be able to handle at least 20
grades (instead of 10). Bring up
adjust.c in an editor
so that it reads 20...#define MAX_GRADES 10
#define MAX_GRADES 20
After the change has been made, when we use make:
% make gcc -ansi -pedantic -Wall -g -c adjust.c gcc -ansi -pedantic -Wall -g -o adjust adjust.o grades.o
it doesn't bother regenerating
grades.o, since the files it
depends on have not changed.
Finally, try changing
grades.h and then re-making...
% make gcc -ansi -pedantic -Wall -g -c adjust.c gcc -ansi -pedantic -Wall -g -c grades.c gcc -ansi -pedantic -Wall -g -o adjust adjust.o grades.o
.c files include that header, they both have to
be recompiled and linked together.