Multiple File Compilation
Multiple File Projects:
Most of the time, full programs are not contained in a single file.
Many small programs are easy to write in a single file, but larger programs
involve separate files containing different modules.
Usually, files are separated by related content.
A class module normally consists of:
- A header file - contains
the declaration of the class (without implementation details)
-
An implementation file - contains implementations of the class members
Filenames:
Header files are usually in the format filename.h,
and implementation files are usually in the format
filename.cpp.
It is a good idea to use the same base filename for corresponding header
and implementation files. Example:
circle.h // header file for a class called Circle
circle.cpp // implementation file for Circle class
Filenames do not have to be the same as the class name, but well-chosen
filenames can help identify the contents or purpose of a file.
When classes are used in a program, the main program would usually be
written in a separate file.
Compilation
The "compilation" of a program actually consitsts of two major stages.
- Compile stage
- Syntax checked for correctness.
- Variables and function calls checked to insure that correct declarations
were made and that they match. (Note:& The compiler doesn't need to
match function definitions to their calls at this point).
- Translation into object code. Object code is just a
translation of your code file -- it is not an executable program, at
this point. (Note: the word "object" in object code does not
refer to the definition of "object" that we use to define
object-oriented programming. These are different terms.)
- Linking stage
- Links the object code into an executable program.
- May involve one or more object code files.
- The linking stage is the time when function calls are matched up
with their definitions, and the compiler checks to make sure it has
one, and only one, definition for every function that is called.
- The end result of linking is usually an executable program.
Putting together a multiple-file project
For a simple example like our Fraction example, it may be tempting to simply use the following
statement inside the main.cpp file:
#include "frac.cpp"
and then just compile the main.cpp file with a single command. This will
work in this example, because it's a linear sequence of #includes -- this
essentially causes the whole thing to be put together into one file as
far as the compiler is concerned.
This is not a good idea in the general case. Sometimes the line-up
of files is not so linear. The separate ideas of compiling and
linking allow these steps to be done separately and there are some
good reasons and benefits:
- Changes to a file require only that file to be re-compiled (rather than everything),
along with the re-linking
- Often, libraries are distributed in pre-compiled format, so trying to #include the .cpp
file would not even be feasible. (A pre-compiled library would still give you the actual
.h file for the #include statements, to satisfy declare-before-use in your own code).
Rule of thumb: Only #include the header files, not the .cpp files!
Example CS account (g++) commands for compilation and linking:
g++ -c frac.cpp // translates frac.cpp into object code, frac.o
g++ -c main.cpp // translates main.cpp into object code, main.o
g++ frac.o main.o -o sample // links object code files into an executable called "sample"
Building basic multiple-file projects
Types of Errors:
- Compilation errors -- usually syntax errors, undeclared variables
and functions, improper function calls.
- Linker errors -- usually involve undefined functions or
multiply-defined functions or symbols
- Run-time errors -- two varieties.
- Fatal -- cause program to crash during execution
- Non-fatal (or logical) -- don't crash the program, but produce
erroneous results.
Compilation and linker errors result in a failed compilation of the
program.
Run-time errors occur while the program is running (after successful
compilation).
Debugging:
Compile stage errors: These are errors that will be reported
by the compiler, which usually provides a filename and line number indicating
where it ran into trouble, for each error reported.
Tips and suggestions:
- Always start at the top of the list of errors.
Fix the first error, then recompile and see what is left.
- If a list of errors is too long, compile and debug one file
at a time.
- When searching for an error, start with the indicated line
number, but also look in the vicinity (usually previous lines) for the
possible error.
- Compile portions of programs as you go -- don't wait until
the program is fully written to do the first compile!
Linking stage errors: These errors, also reported by
the compiler, do not usually contain line numbers, since the linker works
on object code, not on the original source code.
Tips:
- Learn what kinds of problems cause linker errors (usually problems
with agreement between definitions and calls).
- Linker errors usually specify some kind of symbol (used by
the compiler), which often resembles a function or variable name.
This is usually a good clue.
- Learn about good compilation techniques and pre-processor directives
that help avoid linker errors.
Try fixing the compile and linker errors on this
example program.
Run-time errors: These must be tested while running a fully-compiled
program.
Tips:
- To catch fatal errors, try to wedge the mistake between extra
printout statements to locate the cause.
- To catch logic errors, place extra printout statements in code
while testing (to be removed in the finished version). Especially,
print out values of internal variables to locate computation
problems.