Modules, Separate Compilation, Using Make Files


  1. Reusability:

    Let's look at a program that deals with some geometric shapes.

    There are some parts of the program that are generic and could be separated for possible reused.

    What are the parts that are reusable?

  2. Modularizing a program:

    When we separate a set of code into its own files, we call it a module. Although modules can contain sets of functions or sets of classes, we often use a single module for each class.


    Aside: Modules are sometimes called libraries, although the term library is used more often for code that is in an already-compiled form.

    To create a C++ module for our Point class, we need to create 2 new source code files: a .h file and a .cpp file. It is typical for the base name of those files to be the same, and to relate to the class name. Thus, we'll name them:

    Point.h
    This is the header file (or interface) for the module. Those who want to use the class (i.e., in a main program or another module) will have to #include this header file. It contains the class definition.
    Point.cpp
    This is the implementation file for the module. Those who use the Point class must link their code with this code. It contains all the internals of the class, such as the method definitions.

    Since this module only contains the Point class, we'll also need a module for our Rectangle class, and a file for the main program (let's call it main.cpp).

    What files will we need for a Rectangle class module?

    We've already broken the original program (main1.cpp) up into these files and made them available for you to download. Download all those files, including a make file that we'll use shortly.


    Note: Since main.cpp uses the Point class, it includes the Point header:
    #include "Point.h"
    

    When including a non-standard header file (like one you wrote), list its name in double quotes (like above). When including system header files, list their names in angle brackets, as in:

    #include <iostream.h>
    

    How does the implementation file Point.cpp get the Point class definition?

  3. Wrapping header files:

    One thing is missing from our new Point module...The header should be wrapped to prevent multiple inclusion.

    Header files, like our header for the Point class (Point.h) contain class definitions.


    Aside: Headers for modules may also include things like constants, non-class types, and functional prototypes (for module functions that are not part of a class).

    The code in a header should only be included into a .cpp file once; otherwise, the compiler may complain and refuse to compile the .cpp file. If a header does get included into a .cpp file more than once, we call this multiple inclusion.

    Multiple inclusion is likely in the following form:

    #include "Point.h"
    #include "Rectangle.h"
    

    where Rectangle.h includes Point.h. In this case, you might get some compiler error like:

    In file included from Rectangle.h:11,
                     from main.cpp:10:
    Point.h:9: redefinition of `class Point'
    Point.h:20: previous definition here
    

    I.e., it did not like the fact that Point gets defined twice in main.cpp.

    To solve the problem of multiple inclusion, we wrap the header by doing the following... Put the whole header inside an if-not-defined construct, like:

    #ifndef POINT_H
    #define POINT_H
    
    CONTENTS OF Point.h
    
    #endif
    
    where POINT_H is unique to the file Point.h.


    Aside: A typical practice to get a unique symbol (e.g., POINT_H) is to take the file name of the header (e.g., Point.h) and:

    This usually gives a unique symbol.


    Being wrapped, the first time Point.h is included:

    #include "Point.h"  // POINT_H not defined yet,
                        // but once this #include is
                        // done it will be.
    
    the symbol POINT_H will not be defined and the code in the header will be compiled. Later, if the header is included again (directly or indirectly):
    #include "Rectangle.h"  // This tries to include Point.h.
    
    in the same source file, then the code will be skipped since POINT_H is already defined (i.e., it got defined the first time we included Point.h).

    Go ahead and wrap Point.h (i.e., edit Point.h).

  4. Compiling a program made up of multiple parts:

    Now we would like to compile and link our program, which is made up of multiple files, i.e., header (.h) and implementation (.cpp) files.

    We can use g++ as usual, and just need to specify the source code files that make up the program...

    % g++ -o main main.cpp Point.cpp Rectangle.cpp
    


    Note: We DO NOT list the header files. Since they are included into the above files, they will get compiled. Also, the -o main tells it to generate an executable named main instead of a.out.

    We now have an executable main that can be run:

    % main
    

    Enter some values to test it.


    Now, let's change the code. Go into Rectangle.cpp and change the method area() so that it uses the Rectangle method width() (ignore the height() method for now).

    Once you've made the change, we could use the same command to recompile:

    % g++ -o main main.cpp Point.cpp Rectangle.cpp
    

    But wait! This is wasteful because nothing in main.cpp or Point.cpp changed, only Rectangle.cpp, so why should we recompile those other files?!

  5. Compiling source code separately:

    What we should have been doing instead is to use separate compilation, i.e., compile source code to intermediate object files and then link them together.

    First, recall that the program is made up of 5 source files:

    We want to compile each piece of the program separately and then link them together. This will allow us to only regenerate the parts of the program that are affected when we change some source code.

    When we just compile source code (without linking it together), it means that we take the .cpp files and generate intermediate object files (.o).

    To just compile source code, use the -c flag with the compiler...

    % g++ -c main.cpp
    % g++ -c Point.cpp
    % g++ -c Rectangle.cpp
    

    This will generate the object files:

    List your files to see the intermediate object (.o) files.

    Finally, to link the object files (.o) into an executable that we can run, we use the compiler again (although this time it will just pass the .o files on to the linking stage):

    % g++ -o main main.o Point.o Rectangle.o
    

  6. What changes require recompiling/relinking:

    Since we have just compiled the executable main separately on the commandline, we know exactly what commands are needed to generate the pieces that make up the executable.

    Now, let's ask what we need to recompile (or relink) after we change certain files that make up our program...

    For example, suppose we change the file main.cpp...What has to be regenerated?

    Answer: First, recompile main.cpp to get main.o. Then, relink all the object files together again to get the executable main.

    Now, suppose we changed Point.h?


    Note: Whether a .cpp file changes or a header file included (directly or indirectly) by that .cpp file changes, we have to regenerate its corresponding .o file.

    For example, since Rectangle.cpp indirectly includes Point.h (i.e., Rectangle.cpp includes Rectangle.h, which itself includes Point.h), then when Point.h changes, Rectangle.o must be regenerated.


  7. Make Files:

    Since it is tedious to recompile pieces of a program when something changes, people often use the make utility to do it for them.

    The make utility requires that you have a make file that tells it how to compile/link the program.


    Aside: Note the difference between the make utility (a program you run) and a make file (that tells the make utility how to compile/link a specific program).

    A make file should be named either Makefile (first letter uppercase) or makefile (all lowercase). The make file should be in the same directory as the source code files.

    Remember, we already gave you a file called Makefile.


    Aside: Make files can have other file names, but then you'll have to tell the make utility what the name of the make file is (using a commandline option).

    Try using make (with the Makefile you downloaded earlier). First, remove the old executable and object files:

    % rm main *.o
    
    Then, run make with the command:
    % make
    

    Since nothing has been compiled/linked, it will do:

    % g++ -c -o main.o main.cpp
    % g++ -c -o Point.o Point.cpp 
    % g++ -c -o Rectangle.o Rectangle.cpp 
    % g++ -o main main.o Point.o Rectangle.o
    


    Aside: The -o flags it uses with the first 3 g++ commands just make it explicit what the object file names will be. If you have trouble, make sure that the make file is named Makefile and is in the same directory as the source code files.

    Note that it compiled the 3 .cpp files into object files, and then linked the object files into an executable named main.

    Now, type the make command again (let's explicitly tell it what to generate this time) and you get:

    % make main
    make: `main' is up to date.
    

    Here, make tells you nothing has changed, so it has no work to do.

    Next, suppose we want the area() method in the Rectangle class to use the height() method...

    Go ahead and edit the area() method in Rectangle.cpp now. Make sure you save the editted file to disk.

    After editing, when we use make:

    % make
    g++ -c -o Rectangle.o Rectangle.cpp 
    g++ -o main main.o Point.o Rectangle.o
    

    it doesn't bother regenerating main.o or Point.o, since the files those depend on have not changed.


BU CAS CS - Modules, Separate Compilation, Using Make Files
Copyright © 1993-2000 by Robert I. Pitts <rip at bu dot edu>. All Rights Reserved.