Compiling Programs

Steven J Zeil

Last modified: Jan 14, 2024
Contents:

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.

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.

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.

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
  1. Open up an xterm to a Linux server.

  2. Let’s create a directory to work in.

    cd ~/playing
    mkdir javaPie
    cd javaPie
    
  3. Copy the starting files for this Java project.

    cp ~cs252/Assignments/Pie/*.java .
    
  4. Use the ls and more 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.

  5. 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.

  6. 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.

  7. 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 directory at the top of your package structure your source code directory.

Two more things to watch for:

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
  1. Return to our “Pie” program. In an xterm:

    cd ~/playing/javaPie rm *.class

  2. 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 and PieView.java, add one new line at the top:

      package edu.odu.cs.cs252.pie;
      
  3. 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 packlage odu inside edu, a package cs inside odu, and a package cs252 inside cs.

    So we need to create a corresponding nested directory structure:

    mkdir -p edu/odu/cs/cs252
    mv PieSlicer.java edu/odu/cs/cs252
    
  4. 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
    
  5. 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.

  6. 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 &
    
  7. 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 programs java, the compiler searches for code in

  1. Our current working directory (.), and, if can’t find it there,
  2. 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.

  1. 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 and java 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.

  2. You can supply the same path list to a javac or java 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:

  1. cd into the src/ directory and compile from there:

    cd src
    javac -g *.java
    
  2. or compile from the directory above (the one that contains src) and modify the CLASSPATH to add src 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
  1. Return to our “Pie” program. In an xterm:

    cd ~/playing/javaPie
    find edu -name '*.class' -print -exec rm {} \;
    
  2. To create a build directory, we use the -d (for “destination”) option of the javac 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.

  3. 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!" &
    
  4. Clean up is now easy:

    rm -r bin
    
  5. Now let’s set up a designated source code directory. This is usually called src.

    mkdir src
    mv edu src
    tree src
    
  6. 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 .
    
  7. Run the code as before:

    java -cp bin edu.odu.cs.cs252.PieSlicer "I like pie!" &
    
  8. 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
  1. Return to our “Pie” program. In an xterm:

    cd ~/playing/javaPie
    
  2. 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 .
    
  3. 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.)

  4. We can now execute the program from the Jar instead of from the .class files in the bin/ directory:

    java -cp pie.jar edu.odu.cs.cs252.PieSlicer "I like pie!" &
    
  5. 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 .
    
  6. 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 importing 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 importing 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.

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.

 

In the example to the right,

  1. The compilation units are rectangle.cpp and main.cpp.
  2. These are compiled (by g++ -c) commands to produce object code files rectangle.o and main.o.
    • As they are compiled, their #include statements will load the header files rectangle.h and iostream.
  3. 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.
  4. 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 #included 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 #included 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 #includes 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:

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
  1. 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;
    }
    
  2. 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 the mv command to rename it.

  3. 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 #included 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
  1. Create a directory and cd into it.

    mkdir ~/playing/compilation
    cd ~/playing/compilation
    
  2. 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;
    }
    
  3. 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 two g++ invocations created some .o files.

  4. 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.