C Compiling and Makefiles - CS4451
C offers more flexibility in building final executables than most
languages. Unfortunately, as usual, more flexibility means more
to learn. Hopefully this little tutorial will help.
------------------------------------------------
From C to Executable
------------------------------------------------
A final executable program written in C is often actually made of more
than one C source file. These source files are individually turned
into object files (for example, prog.c becomes prog.o) for a specific
machine. Then these object files are linked together to make the
final executable. Even C standard libraries were actually C source at
one point. For instance, if you use the 'fprintf' function, you need
to link the 'stdio' library in with your own object code. The most
useful reason for multiple source files is for libraries. For
instance, the OpenGL library, 'gl', provides routines for 3D graphics.
But even if you don't plan on re-using a portion of code more than
once, you can use multiple source files to break a large project into
smaller, more maintainable pieces.
So, to recap, many source .c files are compiled separately into
many object .o files for a particular machine. Then the .o files
are linked together into one executable for that machine. Some of
these object files may be standard, such as 'gl' or 'stdio'. Don't
expect the object code or executable from an SGI machine to work
on a Sun machine!
------------------------------------------------
Why Didn't I Notice That Before?
------------------------------------------------
The program 'cc' is actually a cover for many smaller programs.
Suppose you write a program all in one file, called 'prog1.c'.
Then the command
cc prog1.c
will create a final program called 'a.out' in one swoop. Actually,
'cc' converts 'prog1.c' into 'prog1.o', then links the code to the 'stdio'
library if necessary and forms 'a.out' Then it removes the 'prog1.o' file.
------------------------------------------------
A Little More Complicated
------------------------------------------------
Suppose that, instead, you broke your code into two pieces, 'prog1.c'
and 'lib1.c', and you wanted to compile it. Again, you can merely type
cc prog1.c lib1.c
The compiler will be smart enough to make 'prog1.o' and 'lib1.o', then
link them together (including the stdio library) and then erase your
object code files.
But let's do it by hand. You CAN take over and move through the steps
yourself. To compile each source file:
cc -c prog1.c
cc -c lib1.c
The '-c' options tells the compiler to convert source code to object code
but then stop. Now you can
cc prog1.o lib1.o -lm
to get an executable. Notice that now, 'prog1.o' and 'lib1.o' are still
around. They were not erased. Also notice that -lm was added because we
apparently needed the math library. Even though the compiler finds the
'stdio' library for you, that's about it.
Hmm. If you can put any number of
object files on a line and have them linked, then you can truly build code in
pieces and put it together. Even the math library and OpenGl libraries
are merely object files. (For reasons we won't go into, they have the
suffix .a instead of .o, though.) If you know where the math library
is, you COULD actually put it in yourself:
cc prog1.o lib1.o /usr/lib/libgl.a /usr/lib/libmath.a
However, the compiler has a shortcut. Since system libraries tend to
be in the '/usr/lib' directory and start with 'lib' and end with '.a',
you can take only the non-repetitive portion of the name and put it after
the '-l' option with no space. This is as good as typing in the full
path and name. For instance '/usr/lib/libgl.a' becomes '-lgl' and
'/usr/lib/libmath.a' becomes ... uh, '-lm'. That's right. To further
shortcut (at the expense of complete confusion) the libmath.a library is
ALSO called just libm.a so you can simply type '-lm'. (On most systems,
'-lmath' should work, though.)
------------------------------------------------
Promises
------------------------------------------------
Obviously, for a program to run, every function that you use must
exist. You can compile an individual .c file even if it doesn't contain all
the functions it uses, IF you PROMISE that they'll be linked in to
the final executable. This is how you do that: each source .c
file can be compiled down to an object .o separately without the
functions it will eventually need. All that is asked is the
functions 'signature' be known. A signature is a function name, what
it returns and what arguments it has. For instance,
void foo( int );
Notice the code for 'foo' is not here, just the return value and its argument
type. Instead of making you remember to type the correct signature of a
function in each of your source .c files, the library
writer may conveniently put all the signatures of his functions into a
header .h file. These can be inserted into your source file, as if you
typed them by hand, by using the include statement:
#include <stdio.h>
#include <math.h>
#include <GL/gl.h>
#include "mypolygon.h"
We, once again, have some shortcut notation. Since system include files
are always in '/usr/include', we leave the path off and put arrows ('<>')
instead of double quotes are the name of the file. There are some directories
inside of '/usr/include' which contain even more header files. If you need
to get inside one of these directories you have to include the rest of the
path after '/usr/include'. That explains ''.
If you break your code into many files, you can make your own include .h
files, of course.
Be careful about forgetting the header file and forgetting to type in the
signature by hand! The C language has a default signature and, if used,
will probably be wrong and will cause your program to bomb!
------------------------------------------------
Makefiles
------------------------------------------------
Unfortunately, there can be many steps involved in compiling a program.
It's easier to keep all these lines in a 'Makefile' and then call 'make'.
A 'Makefile' is a dependency file. There is a line on what can be made and
what you need to make it, then a line on how to make it. For example,
gfx.o: gfx.c
cc -c gfx.c
This line said that 'gfx.o' requires 'gfx.c' to be made. To make it
do a 'cc -c gfx.c'. Likewise,
gfx: gfx.o lib1.o
cc -o gfx lib1.o gfx.o
says that to make the executable file 'gfx', you need the two '.o' files
first. Any number of dependencies can exist in a Makefile.
When gfx.o and lib1.o exist, which may require making them, 'make' can then
call 'cc' and make a file called 'gfx' out of the two '.o' files.
helpful.
On the bad side, the options in a 'Makefile' can get quite ugly because
all sorts of variables and what-not can be used.
On the good side, most software that is complicated enough to need a
'Makefile', tends to come with one. You have an example 'Makefile' which
causes the 'gl', 'glu', and 'aux' libraries to be included with your
single source file.