Compiling Programs
Steven J Zeil
Now that you know how to create and edit files, you can generate new programs. The most commonly used languages in the CS Department at the moment are C++, C, Java, and Python.
1 Compilers versus Interpreters
We write source code as plain text in a programming language. But computers can’t execute plain text. That text needs to be translated into code that can actually be processed on a CPU.
There are two main models for how program source code becomes executable: compilers and interpreters.

A compiler is an executable program that takes program source code (text) as input and translates it into an executable program (binary machine code) that it writes into a file as output. That executable program can then be run to process input data and generate output according to whatever we wrote our program to do.
Once the source code has been compiled, the executable stands alone. You don’t need the source code to execute the program. Of course, if you want to make changes to the program, you need the source code so that you can edit the source code, then re-compile it to create a new executable.
Another important factor to consider: the executable is tied to the CPU and operating system on which it was generated. If you compile a program in Linux on an x86 CPU, you cannot run that executable on a Windows x86 PC, nor on a Linux ARM PC.

An interpreter is an executable program that takes program source code (text) as input, translates it internally to determine what computation it describes, then simulates the execution of that program to process input data and generate output according to whatever we wrote our program to do.
Interpreters are usually easier to work with than compilers, but they are usually slower in executing a program than it would be to run the native executable code generated by a compiler. In addition, if you want to run a program multiple times, in the compiler model you translate it once and then run the executable multiple times. In the interpreter model, you have the extra time penalty caused by re-translating the code each time you want to run the program.
Code written in an interpreted programming language is, to a certain extent, free of the limitation to only be used on the same operating system and CPU architecture as where it was developed. The interpreter program itself is an executable, and that executable is limited to a single operating system and CPU, but if the interpreter has been ported to a different OS/CPU then there is a good chance that your source code can be interpreted there.
These are both idealized models. Real programming languages will add complications to these models.
- In particular, dealing with programs where the source code is divided into multiple files will often introduce complications to these models.
- Furthermore, large, complicated programs will often make use of libraries of code provided by other programmers. Such libraries may be packaged with the compiler/interpreter, may be downloaded as free open source packages, or may be purchased from vendors. Wherever these libraries come from, incorporating them into the compilation or interpretation process will add additional complications.
2 Compiling Java Programs
Java uses a hybrid compiler/interpreter model.
2.1 The Hybrid Model

Java programs get compiled, by the javac
command, into object code for an imaginary CPU called the “Java Virtual Machine” (JVM). This object code is stored in .class
files.
Because this CPU doesn’t really exist, you can’t execute compiled Java code directly. Instead, you run an interpreter, the java
command, that simulates a JVM executing the Java code.
That may seem a little convoluted, but the JVM simulator is much easier to write than a “true” compiler. The JVM simulator is easily ported to many different real machines and operating systems, and serves to isolate the Java program from the fine details of that operating system. As a consequence, Java code that is compiled on one machine can be run on almost any other machine.
“Compile once, run anywhere” is a slogan of the Java community.
2.2 The Basic Java Commands
The command to compile Java code is “javac
” (“c” for compiler) and the command to execute compiled Java code is “java
”. So a typical sequence to compile and execute a single-file Java program would be
javac -g InventoryManager.java
java InventoryManager
Unlike most programming languages, Java includes some important restrictions on the file names used to store source code.
-
Java source code is stored in files ending with the extension “
.java
”. -
Each Java source code file must contain exactly one public class declaration.
-
The base name of the file (the part before the extension) must be the same (including upper/lower case characters) as the name of the public class it contains.
So the command
javac -g InventoryManager.java
compiles a file that must1 contain the code:
public
class InventoryManager ...
The output of this compilation will be a file named InventoryManager.class
(and possibly some other .class
files as well).
If we have a program that consists of multiple files, we can simply compile each file in turn:
javac -g InventoryManager.java
javac -g Storage.java
but this might not be necessary. If one Java file imports or uses another, then the imported file will be automatically compiled if no .class
file for it exists.
So, if the file InventoryManager.java
looked like this
public
class InventoryManager {
public Storage warehouse1;
then compiling InventoryManager.java
would also compile Storage.java
.
- Beware, however, after the first compilation.
javac
by default only checks to see if an appropriately named.class
file exists. If you subsequently make changes toStorage.java
and then recompileInventoryManager.java
, the compiler will not realize thatStorage.java
needs to be recompiled as well.
To run a Java application, we use java
:
java InventoryManager
which looks for a file named InventoryManager.class
. Within that file, it looks for a compiled version of a function Main
that must have been declared this way:
public static
void main (java.lang.String[] args) {
⋮
}
and executes that function.
Example 1: Try This
Open up an
xterm
to a Linux server.Let’s create a directory to work in.
cd ~/playing mkdir javaPie cd javaPie
Copy the starting files for this Java project.
cp ~cs252/Assignments/Pie/*.java .
Use the
ls
andmore
commands to examine the files trhat you have obtained.Which file has the
main
function?
- A quick way to tell is with the command
grep main *.java
Pie.java
provides the central data structure for this program, a “pie” with a slice removed.
PieView.java
contains code to open up a window displaying the pie.Compile this code:
javac -g *.java
It should compile with no compilation errors.
Do an “
ls
” and note the presence of.class
files that indicate that the code has been compiled.Note that you can wind up with more
.class
files than you have.java
files.Run the program:
java PieSlicer &
When running a program, we always give the name of the class that contains the
main
function.Move your mouse around in the window to take different size slices of the pie.
You can use the buttons to change the colors.
End the program by clicking the window’s “X” control when you are done.
You can change the title of the window by supplying a new title as a command line argument. Try:
java PieSlicer "I like pie!" &
2.3 Packages and Directories
As Java programs get larger, programmers usually begin to group their classes into packages. Packages can also be grouped (nested) inside other packages. Again, Java has rules that cause the program’s modular structure to be reflected in the file structure used to store it. If a class is declared to be inside one or more levels of nested packages, then each package name is used as a directory name when storing the source code file.
For example, suppose that we are working in a directory named myProject
and that we had source code like this:
package Project.Utilities;
class Storage {
⋮
and
package Project;
import Project.Utilities.Storage;
class InventoryManager {
⋮
public static
void main (java.lang.String[] args) {
⋮
}
}
then we would need a directory/file structure like this:
-- myProject/
|-- Project/
| |-- InventoryManager.java
| |-- Utilities
| | |-- Storage.java
Inside the Project
directory would be the InventoryManager.java
file and another directory named “Utilities”. The Storage.java
function goes inside that Utilities
directory.
Now, here’s the part that trips up many a Java programmer:
- When you compile and execute code in Java packages, you must always do so from the directory at the top of the package structure. In our example, that would be
myProject
.- If you are configuring a Java project in an IDE, you may need to tell it that your source directory is that same directory.
We call the directory at the top of your package structure your source code directory or source directory, for short.
- A project may have multiple source code directories.
- Most IDEs can do a pretty good job of guessing your source code directories.
- But if your code has mistakes, particularly in the
package
andimport
statements, or if you have not properly arranged your files into the folders implied by yourpackage
statements, the IDE may get confused and guess incorrectly.
- But if your code has mistakes, particularly in the
Two more things to watch for:
- The
javac
compilation command takes the source code filepath. - The
java
execution command takes the name of the class containing themain
function.
So the compilation and execution commands would be
cd MyProject # if we aren't already in there
javac -g Project/Utilities/Storage.java
javac -g Project/InventoryManager.java
java Project.InventoryManager
Notice that the javac
compilation commands use ‘/’ within file paths because, well, that’s how we always write file paths. But the javac
execution command uses a period (.
) because we aren’t giving a file path – we are naming a compiled class. The name of the Java class that started its life inside Project/InventoryManager.java
is Project.InventoryManager
, which means “the class named ‘InventoryManager’ inside the package named ‘Project’”.
Example 2: Try This
Return to our “Pie” program. In an
xterm
:
cd ~/playing/javaPie rm *.class
We are going to put our files into Java packages. Start by editing the Java files:
To
PieSlicer.java
, add some new lines at the top:
package edu.odu.cs.cs252; import edu.odu.cs.cs252.pie.Pie; import edu.odu.cs.cs252.pie.PieView
To
Pie.java
andPieView.java
, add one new line at the top:
package edu.odu.cs.cs252.pie;
Java has a rule about packages and directories. Each package name must be matched by a directory name. And the package name “edu.odu.cs.cs252” is actually 4 different packages, a package
edu
, a packlageodu
insideedu
, a packagecs
insideodu
, and a packagecs252
insidecs
.So we need to create a corresponding nested directory structure:
mkdir -p edu/odu/cs/cs252 mv PieSlicer.java edu/odu/cs/cs252
Then we need to create a
pie
directory to hold the other two Java files:
mkdir edu/odu/cs/cs252/pie mv Pie.java edu/odu/cs/cs252/pie mv PieView.java edu/odu/cs/cs252/pie
Do a
tree edu
to be sure that everything is in the right place. You should see something like this:
edu `-- odu `-- cs `-- cs252 |-- pie | |-- Pie.java | `-- PieView.java `-- PieSlicer.java
Now let’s compile our repackaged code:
javac -g edu/odu/cs/cs252/pie/*.java javac -g edu/odu/cs/cs252/PieSlicer.java
Do a
tree edu
and take note of how the
.class
files have distributed themselves next to the corresponding.java
files.Now run the program. When we compile Java code, we give paths to the files to be compiled. When we run Java programs, we give the class name rather than the file path:
java edu.odu.cs.cs252.PieSlicer &
Because the
.class
files have been distributed through the directory structures, “cleaning” a Java programming project is bit messier once packages are involved.Try the following:
tree edu find edu -name '*.class' -print -exec rm {} \; tree edu
2.4 The CLASSPATH
Java’s special rules about how to name the source code files and the directories where they live are aimed at one idea: any time our code mentions a class that isn’t in the file being compiled or executed, Java can use those rules to find the .java
or .class
files containing that class. So if our code makes use of a class named edu.odu.cs.Example
, the compiler knows to look inside a directory edu/odu/cs/
for a file named either Example.java
or Example.class
.
But edu/odu/cs
is a relative path. Where does Java start its search from? In other words, where does it look for the edu/
directory?
By default, when we compile with a
javac
command or run programsjava
, the compiler searches for code in
- Our current working directory (
.
), and, if can’t find it there,- inside the compiler’s own pre-built libraries.
You can control everything except the final step of searching through the pre-build system libraries by manipulating the Java CLASSPATH, the list of directories where the Java programs search for code. This is done in one of two ways.
-
You can set an environment variable named CLASSPATH to the appropriate list. e.g.:
export CLASSPATH=.:/home/myLoginName/libraries/myJavaCode/
A CLASSPATH is a list of paths to directories, separated by ‘:’. So the above command would tell all subsequent
javac
andjava
commands to first look in my current working directory (.
) and then to look in/home/myLoginName/libraries/myJavaCode/
, where, presumably, I have placed some of my favorite Java classes for later use. -
You can supply the same path list to a
javac
orjava
command via a-cp
option, e.g.,javac -g -cp .:/home/myLoginName/libraries/myJavaCode/ edu/odu/cs/InventoryManager.java java -cp .:/home/myLoginName/libraries/myJavaCode/ edu.odu.cs.InventoryManager
2.4.1 Source and Binary Directories
Because cleaning a project that has multiple packages is a bit awkward and prone to mistakes that could delete the valuable source code, it is common for Java projects to feature designated source directories and build directories, where a build directory holds only the files produced by the compiler and can, therefore, be safely deleted without risking the source code.
Compiling code in different source and build directories requires us to make use of the CLASSPATH that we have just discussed.
For example, it’s common to put all of the Java source code into a directory named src
. To compile that code you can do one of two things:
-
cd
into thesrc/
directory and compile from there:cd src javac -g *.java
-
or compile from the directory above (the one that contains
src
) and modify the CLASSPATH to addsrc
as a source directory:javac -g -cp src src/*.java
It’s also common to put all of the compiled .class
files inside a bin/
directory. To do this, we need to tell the javac
command to write things into the bin
directory, which is done with a -d bin
option, but we also need to tell it to look for already compiled stuff in that same directory, which is done by adding bin
to the CLASSPATH:
javac -g -d bin -cp bin *.java
If we want to have both of these refinements, source code inside src
and compiled binaries inside bin
, we need to add both directories to the CLASSPATH:
javac -g -d bin -cp bin:src src/*.java
Example 3: Try This
Return to our “Pie” program. In an
xterm
:
cd ~/playing/javaPie find edu -name '*.class' -print -exec rm {} \;
To create a build directory, we use the
-d
(for “destination”) option of thejavac
command to redirect the compiler output into a different location:
javac -g -d bin edu/odu/cs/cs252/pie/*.java javac -g -d bin edu/odu/cs/cs252/PieSlicer.java tree .
Note that we now have a
bin
directory with all of our.class
files.To run the program, use the
-cp
option to tell the Java VM where to look for our.class
files:
java -cp bin edu.odu.cs.cs252.PieSlicer "I like pie!" &
Verify for yourself that, without the
-cp
, this does not work:
java edu.odu.cs.cs252.PieSlicer "I like pie!" &
Clean up is now easy:
rm -r bin
Now let’s set up a designated source code directory. This is usually called
src
.
mkdir src mv edu src tree src
We can compile by using the
-cp
option to tell the compiler where we have stored our source code.
javac -g -d bin -cp bin:src edu/odu/cs/cs252/pie/*.java javac -g -d bin -cp bin:src edu/odu/cs/cs252/PieSlicer.java tree .
Run the code as before:
java -cp bin edu.odu.cs.cs252.PieSlicer "I like pie!" &
And, again, cleanup is easy.
rm -r bin
2.5 Libraries in Jars
For projects with many packages, the compiled code is often packaged up into a single file, called a jar, with a file extension of “.jar”. This makes it easy to distribute libraries or even an entire program as a single file. A jar file is actually a conventional “zip” compressed archive file with a little bit of extra directory information written into a special file called the manifest,2 included in the archive.
If we want to compile and execute code that makes use of library code in a jar, we add the path to that jar file to the CLASSPATH. For example, if I have a directory named libs/
containing a pair of jars, library1.jar
and library2.jar
, I would say:
javac -g -cp .:lib/library1.jar:lib/library2.jar edu/odu/cs/InventoryManager.java
java -cp .:lib/library1.jar:lib/library2.jar edu.odu.cs.InventoryManager
In a situation where I have multiple jars in a single directory, I can use a sort-of-wildcard to simplify the path:
javac -g -cp '.:lib/library1.*' edu/odu/cs/InventoryManager.java
java -cp '.:lib/library1.*' edu.odu.cs.InventoryManager
The quotes are required. This isn’t a real command shell wildcard, and we don’t want the command shell to try to expand the ‘*
’ – we want the ‘*
’ to be passed, unchanged, to the Java commands.
2.5.1 Executing a Jar
A jar file can contain entire programs. Sometimes there is a single preferred program specified in the manifest. If so, you can execute this program by simply saying
java -jar pathToTheJarFile
If there is no preferred program or you want to execute a program other than the preferred one, you use the normal java command to name the class containing the desired main
function, but use the -cp
option (described below) to tell the VM to look inside the jar file, e.g.,
java -cp myLargeProgram.jar JarProject.AProgramInAJar
Example 4: Try This
Return to our “Pie” program. In an
xterm
:
cd ~/playing/javaPie
Compile the code into the
bin/
directory.
javac -g -d bin -cp src edu/odu/cs/cs252/pie/*.java javac -g -d bin -cp src edu/odu/cs/cs252/PieSlicer.java tree .
Now let’s create a basic jar file:
jar cf pie.jar -C bin .
To see what is in that jar, do
unzip -l pie.jar
(Java jars are really Zip archives with some specific required content.)
We can now execute the program from the Jar instead of from the
.class
files in thebin/
directory:
java -cp pie.jar edu.odu.cs.cs252.PieSlicer "I like pie!" &
Another possibility is to create a Jar that “knows” what class holds the
main
function.
jar cfe pie.jar edu.odu.cs.cs252.PieSlicer -C bin .
Now running the code is simpler:
java -jar pie.jar "I still like pie!" &
2.6 Compiler Options
As with most compilers, there are several options that you may choose to employ when compiling and executing Java code. Here is a summary of the most commonly used ones:
Commmon Command Options | |
---|---|
javac command (compiling) |
|
-cp pathlist | Add the directories and jar files named in the pathlist (multiple items may be separated by ‘:’) to the list of places searched when import ing other Java source code. |
-g | Include debugging information in compiled code (required if you want to be able to run the code in a debugger. |
-d path-to-directory | Put all compiled .class files into this directory, instead of mixing them in with the source code. |
-depend | Check each imported class to see if its source code has been changed since it was last compiled. If so, automatically recompile it. |
-deprecation | Check the code for features that used to be legal in Java, but are expected to become illegal in the near future. |
-O | Optimize the compiled code (produces smaller, faster programs but takes longer to compile) |
java command (executing) |
|
-cp pathlist | Add the directories and jar files named in the pathlist (multiple items may be separated by ‘:’) to the list of places searched when import ing other Java source code. |
-jar path-to-jar-file | Run the program stored as the “main” class in a jar. |
jar command (creating jars) |
|
cf jar-file-name | Create a jar file with the indicated name. |
-C path-to-directory | Look in this directory for the files to put into the jar. |
list-of-files-and-directories | The -C path gets prepended onto this list of files and directories that follows, and that combination gives the paths to the files to be put into the jar. |
3 Compiling C and C++
C++ and its ancestor language, C, are translated via compilers.
The most popular C++ and C compilers are g++
and gcc
. (Actually, gcc
and g++
are aliases for the same compiler being invoked with slightly different options.)
3.1 The Structure of C++ and C Programs
Although not really a Unix-specific topic, it’s hard to discuss how to compile code under any operating system without a basic understanding how programs are put together.
The source code for a C++ (or C) program is contained in a number of text files called source files. Very simple programs might be contained within a single source file, but as our programs grow larger and more complicated, programmers try to keep things manageable by splitting the code into multiple source files, no one of which should be terribly long.
There are two different kinds of source files: header files and compilation units. Header files are generally given names ending in “.h”. Compilation unit files are generally given names ending in “.cpp” for C++ code and “.c” for C code.
- There are variations of these file extensions, particularly for C++. Other less common endings accepted by some C++ compilers for non-header files include “.C” “.cc” and “.cxx”.
Header and non-header (compilation unit) files are treated differently when we build programs. Each compilation unit is compiled separately from the others (Figure 1). This helps keep the compilation times reasonable, particularly when we are fixing bugs in a program and may have changed only one or two non-header files. Only those changed files need to be recompiled.
- The compilation units are
rectangle.cpp
andmain.cpp
. - These are compiled (by
g++ -c
) commands to produce object code filesrectangle.o
andmain.o
.- As they are compiled, their
#include
statements will load the header filesrectangle.h
andiostream
.
- As they are compiled, their
- Those two object code files are linked (by a
g++
command) to produce the executable file,program
.- The same base command,
g++
, is used for compiling and linking. It compiles when given a.cpp
file to work on, and links when it is given one or more.o
files.
- The same base command,
- You can then run that program by simply giving a path to it on the command line, e.g.,
./program
A summary of C++ compilation:
- Compilation units (usually
.cpp
files) are compiled to produce object code files (.o
).
- Header files (Usually
'h
files for programmer-supplied headers,], but “system” headers that are included with the compiler usually have no extension) are not compiled directly, but their contents get#include
d into compilation units when those are compiled.- Object code files are linked to form an executable program. The files of an executable usually have no extension.
- The executable can then be run from the command line
Header files are not compiled directly, Instead, header files are included into other source files via #include
. In fact, when you invoke a C/C++ compiler, before the “real” compiler starts, it runs a pre-processor whose job is to handle the special instructions that begin with #
. In the case of #include
statements, the pre-processor simply grabs the relevant header file and sticks its content into the program right at the spot of the #include
.
This can result in a dramatic increase in the amount of code that actually gets processed. The code shown here, for example, is pretty basic. But the #include
statements bring in an entire library of I/O and string-related declarations from the C++ standard library. Here, for example, is the output of the pre-processor for one compiler for that small block of code. (If you look at the very end, you can recognize the main code for this program.)
A header file can be #include
d from any number of other header and non-header files. That is, in fact, the whole point of having header files.
Header files should contain declarations of things that need to be shared by multiple other source files.
Compilation unit files should declare only things that do not need to be shared.
As we go through all the compilation steps required to build a program, anything that appears in a non-header file will be processed exactly once by the compiler. Anything that appears in a header file may be processed multiple times by the compiler.
3.1.1 What Goes Into a Header File? What Goes Into a Non-Header File?
The short answer is that a header file contains shared declarations, a non-header file contains definitions and local (non-shared) declarations.
3.1.2 What is the difference between a declaration and a definition?
Pretty much everything that has a “name” in C++ must be declared before you can use it. Many of these things must also be defined, but that can generally be done at a much later time.
You declare a name by saying what kind of thing it is:
const int MaxSize; // declares a constant
extern int v; // declares a variable
void foo (int formalParam); // declares a function (and a formal parameter)
class Bar{...}; // declares a class
typedef Bar* BarPointer; // declares a type name
In most cases, once you have declared a name, you can write code that uses it. Furthermore, a program may declare the same thing any number of times, as long as it does so consistently. That’s why a single header file can be included by several different non-header files that make up a program - header files contain only declarations.
You define constants, variables, and functions as follows:
const int MaxSize = 1000; // defines a constant
int v; // defines a variable
void foo (int formalParam) {++formalParam;} // defines a function
A definition must be seen by the compiler once and only once in all the compilations that get linked together to form the final program. A definition is itself also a declaration (i.e., if you define something that hasn’t been declared yet, that’s OK. The definition will serve double duty as declaration and definition.).
When a compilation unit is compiled, we get an object-code file, usually ending in “.o”. These are binary files that are “almost” executable - for some variables and functions, instead of the actual address of that variable/function, they still have its name. This happens when the variable or function is declared but not defined in that compilation unit (after expansion of #include
s by the pre-processor). That name will be assigned an address only when a file containing a definition of that name is compiled. And that address will only be recorded in the object code file corresponding to the compilation unit source file where the name was defined.
The complete executable program is then produced by linking all the object code files together. The job of the linker is to find, for each name appearing in the object code, the address that was eventually assigned to that name, make the substitution, and produce a true binary executable in which all names have been replaced by addresses.
Understanding this difference and how the entire compilation/build process works (Figure 1) can help to explain some common but confusingly similar error messages:
-
If the compiler says that a function is undeclared, it means that you tried to use it before presenting its declaration, or forgot to declare it at all.
-
The compiler never complains about definitions, because an apparently missing definition might just be in some other non-header file you are going to compile later. But when you try to produce the executable program by linking all the compiled object code files produced by the compiler, the linker may complain that a symbol is
-
undefined (none of the compiled files provided a definition) or is
-
multiply defined (you provided two definitions for one name, or somehow compiled the same definition into more than one object-code file).
For example, if you forget a function body, the linker will eventually complain that the function is undefined. If you put a variable or function definition in a .h file and include that file from more than one place, the linker will complain that the name is multiply defined.
-
3.2 Compiling a Program With Only One Compilation Unit
The simplest case for each compiler involves compiling a single-file program or, in general, a program with one compilation unit and some headers.
Example 5: Try This
Use an editor (e.g., emacs) to prepare the following files:
hello.cpp
#include <iostream> using namespace std; int main () { cout << "Hello from C++ !" << endl; return 0; }
hello.c
#include <stdio.h> int main () { printf ("Hello from C!\n"); return 0; }
To compile and run these, give the commands:
g++ -g hello.cpp ls
Notice that a file a.out has been created.
./a.out gcc -g hello.c ./a.out
The compiler generates an executable program called
a.out
. If you don’t like that name, you can use themv
command to rename it.Alternatively, use a
-o
option to specify the name you would like for the compiled program:g++ -g -o hello1 hello.cpp gcc -g -o hello2 hello.c ls ./hello1 ./hello2
In the example above, we placed “./” in front of the file name of our compiled program to run it. In general, running programs is no different from running ordinary Unix commands. You just type
pathToProgramOrCommand parameters
In fact, almost all of the “commands” that we have used in this course are actually programs that were compiled as part of the installation of the Unix operating system.
As we have noted earlier, we don’t usually give the command/program name as a lengthy file path. We say, for example, “ls” instead of “/bin/ls”. That works because certain directories, such as /bin
, are automatically searched for a program of the appropriate name. This set of directories is referred to as your execution path. Your account was set up so that the directories holding the most commonly used Unix commands and programs are already in the execution path. (You can modify your execution path, if desired, to add additional directories.) You can see your path by giving the command
echo $PATH
One thing that you will likely find is that your $PATH
probably does not include “.”, your current directory. Placing the current directory into the $PATH is considered a (minor) security risk, but that means that, if we had simply typed “a.out” or “hello”, those programs would not have been found because the current directory is not in the search path. Hence, we gave the explicit path to the program files, “./a.out” and “./hello”.
3.3 Compiling With Multiple Compilation Units
A typical program will consist of many .cpp
files. (See Figure 1.) Usually, each class or group of utility functions will have their definitions in a separate .cpp
file that defines everything declared in the corresponding .h
file. The .h
file can then be #include
d by many different parts of the program that use those classes or functions, and the .cpp
file can be separately compiled once, then the resulting object code file is linked together with the object code from other .cpp
files to form the complete program.
Splitting the program into pieces like this helps, among other things, divide the responsibility for who can change what and reduces the amount of compilation that must take place after a change to a function body.
When you have a program consisting of multiple files to be compiled separately, add a -c
option to each compilation. This will cause the compiler to generate a .o
object code file instead of an executable. Then invoke the compiler on all the .o
files together without the -c
to link them together and produce an executable:
g++ -g -c file1.cpp
g++ -g -c file2.cpp
g++ -g -c file3.cpp
g++ -g -o programName file1.o file2.o file3.o
(If there are no other .o files in that directory, the last command can often be abbreviated to “g++ -o programName -g
*.o
”.) The same procedure works for the gcc
compiler as well.
Actually, you don’t have to type separate compilation commands for each file. You can do the whole thing in one step:
g++ -g -o programName file1.cpp file2.cpp file3.cpp
But the step-by-step procedure is a good habit to get into. As you begin debugging your code, you are likely to make changes to only one file at a time. If, for example, you find and fix a bug in file2.cpp
, you need to only recompile that file and relink:
g++ -g -c file2.cpp
g++ -g -o programName file1.o file2.o file3.o
Example 6: Try This
Create a directory and
cd
into it.
mkdir ~/playing/compilation cd ~/playing/compilation
Use an editor to prepare the following files in that directory:
hellomain.cpp
#include <iostream> #include "sayhello.h" using namespace std; int main () { sayHello(); return 0; }
sayhello.h
#ifndef SAYHELLO_H #define SAYHELLO_H void sayHello(); #endif
sayhello.cpp
#include <iostream> #include "sayhello.h" using namespace std; void sayHello() { cout << "Hello in 2 parts!" << endl; }
To compile and run these, give the commands:
g++ -g -c sayhello.cpp g++ -g -c hellomain.cpp ls g++ -g -o hello1 sayhello.o hellomain.o ls ./hello1
Note, when you do the first
ls
, that the first twog++
invocations created some.o
files.Alternatively, you can compile these in one step. Give the command
rm hello1 *.o ls
just to clean up after the previous steps, then try compiling this way:
g++ -g -o hello2 hellomain.cpp sayhello.cpp ls ./hello2
An even better way to manage multiple source files is to use the make
command.
3.4 Some Useful Compiler Options
Another useful option in these compilers is -D
. If you add an option -Dname=value
, then all occurrences of the identifier name in the program will be replaced by value. This can be useful as a way of customizing programs without editing them. If you use this option without a value, -Dname
, then the compiler still notes that name has been “defined”. This is useful in conjunction with compiler directive #ifdef
, which causes certain code to be compiled only if a particular name is defined. For example, many programmers will insert debugging output into their code this way:
⋮
x = f(x, y, z);
#ifdef DEBUG
cerr << "the value of X is: " << x << endl;
#endif
y = g(z,x);
⋮
The output statement in this code will be ignored by the compiler unless the option -DDEBUG
is included in the command line when the compiler is run.
Sometimes your program may need functions from a previously-compiled library. For example, the sqrt
and other mathematical functions are kept in the “m
” library (the filename is actually libm.a
). To add functions from this library to your program, you would use the “-lm
” option. (The “m” in “-lm
” is the library name.) This is a linkage option, so it goes at the end of the command:
g++ -g -c file1.cpp
g++ -g -c file2.cpp
g++ -g -c file3.cpp
g++ -g -o programName file1.o file2.o file3.o -lm
The general form of gcc/g++ commands is
g++ compilation-option files linker-options
Here is a summary of the most commonly used options for gcc/g++:
Compilation Flags | |
---|---|
-c | compile only, do not link |
-o filename | Use filename as the name of the compiled program |
-Dsymbol=value |
Define symbol during compilation. |
-g | Include debugging information in compiled code (required if you want to be able to run the gdb debugger). |
-O | Optimize the compiled code (produces smaller, faster programs but takes longer to compile). Different levels of optimization can be invoked as -O1 , -O2 |
-I directory | Add directory to the list of places searched when a “system” include (#include <...> ) is encountered. |
Linkage Flags | |
-L directory | Add directory to the list of places searched for pre-compiled libraries. |
-l_libname_ | Link with the precompiled library lib _libname_.a |
4 Interpreting Python
Python programs are usually translated using an interpreter.

A Python program may be divided among multiple files.
The python interpreter program, python3
, can be told the name of the main Python source code file. If that file contains import
statements, those statements can name other Python files in the same directory. import
statements can also name libraries of code stored elsewhere on the system. Either way, the python3
interpreter will draw in those other files and integrate them into a single program, which it will them simulate/execute.
Many libraries are provided as part of the default python3
installation package. If you want to use other libraries that have been published to the world-wide Python infrastructure, you may need to use the pip
command to install the library into your account. The command
pip3 list
will list all installed packages. If the one you want is not available, then
pip3 install numpy
will fetch and install that library, making it available to your Python programs.
1: Actually there are a few other possibilities, because not everything in Java is a class
. You could instead have an interface
, but it would also need to be public and have a name that matched the file name.
2: In shipping, a “manifest” is a list of goods or people being transported somewhere. E.g., a list of all cargo loaded onto a truck or a list of passengers on a plane.