-----

Multi-file programs

-----

A large C or C++ program should be divided into multiple files. This makes each file short enough to conveniently edit, print, etc. It also allows some of the code, e.g. utility functions such as linked list handlers or array allocation code, to be shared with other programs.

Dividing by topic

In general, divide your code along natural divisions in the program. Each file should contain a group of functions that do related things, e.g. manipulate the same data structure, are associated with the same class or closely-related group of classes, etc. Imagine another program that might use some of these functions: include in one file all the functions that this other program would require. Or try to write an English description of what's in each file: good divisions can usually be described succinctly.

Examples:

A large program might be divided into several such files, plus a main program file. The main file contains the function main, as well as miscellaneous functions that don't seem to fit anywhere else and/or seem specialized to this particular program.

There is no one "right" way to divide up a large program. Some programmers divide their programs into many, very tiny files. Some use a smaller number of larger files. The best choice depends not only on individual taste but on what sort of task the program is doing.

Compiling

To compile a multi-file program, first compile each of the non-main files of code. For example, a file of array manipulation functions might be compiled as follows:

g++ -c arrays.C -Wall

The -c flag tells the compiler to produce an output file (which will be named arrays.o) that can be linked together with other compiled files. It also prevents the compiler from complaining about the lack of a main function and about use of functions that are not defined (i.e. because they are defined in other code files). The -Wall flag turns on all the compiler warnings: a good idea, in general.

When you have done this for all your non-main code, you will have a collection of files with extension .o, called "object files." The final step in compiling is to compile the main file, linking in all the object files. This is done as follows:

g++ -o myprogram myprogram.C arrays.o fileio.o matrix.o -Wall

The -o flag tells the compiler to produce an output (executable) file named myprogram. If you leave off this option, the executable will be named a.out. The object files are listed on the command line after the main file.

When it links all these programs together, the C++ compiler matches up each function calls with the definitions of that function. This will work automatically, even if the function is defined in a different code file. The compiler will complain if you use a function which is never defined, e.g. if you forgot to include one of your object files.

Function prototypes

Prototypes (also called declarations) are used when the compiler must be informed about a function at a point when it is inconvenient to give the compiler the full code for the function. For example:

Some programmers routinely start a file of code with prototypes for all functions defined in that file, so that they never have to worry about the order in which they give the full code for the functions.

Prototypes contain only type information for the function, but not its actual code. See the following examples, in which token is a user-defined class.

int min (int, int);              // no parameter names
double cbrt(double x);           // with a parameter name
int token_is_EOF(token input);
token smart_read(ifstream &fp);

A simple way to write a prototype is to copy the first line of the function definition and append a semicolon. Notice that names for input parameters can be included or omitted in the prototype. The compiler doesn't use them, but they may be helpful to you or to other people reading your code.

A prototype for a function can be put in the file of code which calls the function, typically at/near the top of the file. Alternatively, the prototype can be put in a header file (see below) and an include statement for this header file inserted into the code file.

Header files

Declarations are frequently moved into separate files, called header files. If a piece of code is named foo.C, the corresponding header file is traditionally named foo.h. You can give it a different name, but only do this when there is a clear reason for the strange name. A typical reason might be that some group of declarations are shared by several files of code, none of which can claim primary ownership of the declarations.

The main purpose of header files is to make definitions and declarations accessible to functions in more than one file. For example, if functions from two files need to access the same global constant (e.g. a definition of the constant PI), that variable should be defined in a header file. Conversely, if a declaration is only meant to be used by the functions in one code file, leave it in that code file and do not put it in a header file.

Thus, your header file might contain such items as

In general, header files should not contain code for functions. Exceptions involve very short functions, so short that they can be included in a class definition or declared as inline. These functions do trivial things such as increment a counter, return the larger of two numbers, or return the value of a private data member for a class.

If the function does something non-trivial, it belongs in a code file. If functions in another code file need to be able to call it, put a prototype (declaration) for the function, but not the actual code for the function, in the header file. (See above for examples of prototypes.)

When you have built a header file foo.h, each code file which uses definitions from foo.h should contain an "include" statement. This directs the compiler to read the code in foo.h when it compiles the code file. It is as if the material in foo.h was inserted, as text, into your code file.

#include "foo.h"

These include statements are like the include statements for system libraries (e.g. iostream.h, math.h), except that the filename is enclosed in quotation marks rather than angle brackets. The include statement should be near the top of the code file, before any code that makes reference to its definitions. It is legal for an include file can contain include statements, directing the compiler to load further files.

When using complicated sets of nested include files, programs may end up loading an include file twice. In such cases, special precautions must be taken to prevent compiler errors. Until you have more programming experience, it is best to avoid loading a header file more than once. Exception: system header files such as iostream.h have typically been written so that they can safely be loaded multiple times.


This page is maintained by Geoff Kuenning.