Brief Review of Multiple-File Compilation

This discussion applies only to UNIX and UNIX-like (e.g. Linux, or gcc ports for the PC) development environments.  If you are using something else, you need to learn how to achieve the same results -- the broad concepts are the same anywhere, but your IDE may do things a little differently.

Example

Here are three files containing a simple example of these concepts: date.h and date.c contain a module for manipulating date values (somewhat as discussed in class). main.c containas a short main program that uses this module. Of course, most modules are a bit more complex than this one; this is just for illustration purposes.

Why Multiple-File Compilation?

Primarily to allow your code to be more easily reused.  For example, if you wrote this example program with all the source code in one file, then the next time you wanted to use a DATE, you'd have to copy the code out of there (probably making some mistakes in the process) and recompile it.  By breaking a program into a handful of conceptually related pieces (modules), you both make the code easier to use later and you clarify (to yourself and to any other readers) the relationships among pieces of your program.

How Big Should a ModuleBe?

There's no right answer.  Big enough to contain a chunk of conceptually related code, but no bigger.  For example, you probably should not have a module that contains both DATE-related code and code that processes user input; these are conceptually distinct.  That is, some program may use DATE functions while it's processing input, but it's certainly not a requirement of every C program. Similarly, DATE code may be used for a variety of purposes beyond this one simple program, so by keeping that code together, we can easily insert it in some other project.

What Goes in the Header File?

Two things:
"public" declarations
That is, anything a programmer using your code needs to know about.  In C, this generally includes typedefs and function prototypes, sometimes constants (think about the stdio.h header:  it provides, for example, the definition of the FILE type; prototypes for printf, scanf, etc.; and constants like EOF, stdin, stdout, and stderr).  In C++, this generally includes class declarations plus any supporting typedefs and constants.

Notice that in both cases (C and C++), the header file does not contain any function definitions (i.e. bodies) -- a programmer using the module shouldn't need to know how a function accomplishes its task; she just needs to know what the function's task is and how to use it.

pre-processor directives
A couple different kinds of these.  First, if the header file requires anything defined in another header file (most likely a type; maybe you declare a function that requires a FILE parameter and therefore need the stdio.h header), the required header file must be #include-d at the top of the header file as you're accustomed to doing.

More importantly, you must guard against the header file being #include-d more than once into a program that's being compiled.  Most compilers don't mind seeing several declarations of the same function (as long as they agree with each other!), but will complain if a type is typedef-ed more than once (even if the definitions are identical).  We can exploit the pre-processor to manage this for us:  the first two lines of your header file should be

#ifndef FILENAME_H
#define FILENAME_H

(where FILENAME is replaced by the actual name of the file; e.g. mystuff.h should start with #ifndef MYSTUFF_H -- (e.g. that strfuncs functions need the Set class). the specific format of this name isn't important, but it should be closely related to the filename) and the last line should be

#endif

This isn't the place for a treatise on the pre-processor, but essentially these lines say "if you've never seen this file before, include these definitions; otherwise pretend you didn't see it."

What Goes in the Source File?

First, #include the corresponding header file (so mystuff.cpp should #include "mystuff.h").  In some cases, the header file definitions aren't required for successful compilation, but including them ensures that (for example) the function definitions match the function declarations.  You should also (of course) #include any other headers required by your code (and don't worry about #include-ing them too many times -- they're all protected by the mechanism described above).

Then, define all the functions you need.   At the very least, this means all the functions declared in the header;  you can also declare "helper" functions that exist only to help implement the functions declared in the header (and therefore don't need to be "exposed" to the public).  If these helper functions are members of a (C++) class, then they of course are declared in the header, but otherwise they needn't be.  To enforce the restriction that these functions are only to be used to implement your functions, you can/should declare each helper function as static, which means that it can only be invoked by functions defined in the same file.

Note that typedefs, etc., already declared in the header do not need to be redeclared here (because you're #include-ing the header directly).

How do I Use a Module?

(This refers only to user-defined modules, not to standard modules/libraries).

One of the nicest things about modular construction is that one you have the module written correctly, you need only compile it once, then just link it with whichever programs need it.  Of course, a module can't be compiled into an executable program, because it lacks a main() (and no module should ever contain a main()!), but it can be compiled into machine code nonetheless; this sort of file is often called an object file (no close relationship to C++ objects).  So to compile our strfuncs module this way, we would write

acc -c strfuncs.c

or

CC -c strfuncs.c

(depending on whether we were using any C++ in the implementation of strfuncs).  The result will be a file called strfuncs.o.

To use strfuncs in a program, first #include "strfuncs.h" at the top, as usual.  This provides the compiler with the necessary definitions and function declarations (but notice that the function definitions are not "brought in" here -- that's the next step).

To compile the program (say it's called example.c), we have to list all the files the compiler/linker will need:  example.c, of course, and also the object file (e.g. that strfuncs functions need the Set class). containing the function implementations for strfuncs (that is, strfuncs.o):

acc example.c strfuncs.o

This will produce a perfectly usable a.out; to produce a differently-named executable, use the -o option as usual.