The Structure of a C++ Program

Steven Zeil

Last modified: Sep 5, 2017
Contents:

1 Separate Compilation

C++ programs can range from a handful of statements to hundreds of thousands, and may be written by one person or by a team.

Beginning C++ programmers usually work with programs in which all of the source code is written into a single file.

Putting your entire program into a single file is OK for small programs. But with large programs

By splitting a program up into multiple files that can be separately,

1.1 The Files of a C++ Program

A typical C++ program is divided into many source code files

1.2 How C++ Code is Compiled

 

Object Code is

Linking mainly consists of replacing those symbols in object code by real addresses. So linking can only be done after all the source code has been compiled into object code.

Linking is much simpler and therefore much faster than the earlier compiling steps.

So, think about how this works with large, complicated projects.

On large projects with hundreds or thousands of files,

So separating the linking step allows these large programs to be rebuilt quickly, because not every line of code needs to be put through the entire compilation process.

1.3 Pre-processing

The # Preprocessor

 

The preprocessor runs before the compiler proper.

The preprocessor:

The common pre-processor instructions are

1.3.1 #include

Example: #include (simple case)

g++ -E C.cpp > C.i

A more realistic example

In real programs, most of the code actually seen by the compiler may come from #includes

#include <iostream>

using namespace std;

int main() {
  cout << "Hello World" << endl;
  return 0;
}

Deja-Vu

 

This distinction will be important later.

1.4 Other Pre-processing Commands

#define

A common example of #define is to provide special system-specific constants, e.g.,

#define VersionNumber "1.0Beta1"

int main() {
   cout << "Running version "
       << VersionNumber
       << endl;

Much more elaborate macros are possible, including ones with parameters.


#ifdef, #ifndef, #endif

Used to select code based upon whether a macro has been defined:

#ifdef __GNUG__
  /* Compiler is gcc/g++ */
#endif
#ifdef _MSC_VER
  /* Compiler is Microsoft Visual C++ */
#endif


#if, #define, and #include

g++ -E C2.cpp > C2.i

The result is file C2.i.

2 Declarations and Definitions

Some of the most common error messages you will encounter as a C++ programmer are

Fixing these requires that you understand the difference between declarations and definitions.

Warning: Textbooks & C++ websites are often sloppy about this terminology.

(Error messages from compilers, on the other hand, tend to be very precise about it, and assume that you understand the difference.)

2.1 Declarations

A declaration in C++

2.2 Definitions

A definition in C++

General rules for declarations & definitions:

  • All definitions are also declarations.
    • But not vice versa
  • A name must be declared before you can write any code that uses it.
  • A name can be declared any number of times, as long as the declarations are identical.
  • A name must be defined exactly once, somewhere within all the separately compiled files making up a program.

2.3 Decls&Defs: Variables

2.4 Decls&Defs: Functions

2.5 Decls&Defs: Data Types

3 Dividing code into modules

Most of the source code files that make up a program are divided into modules consisting of a header file and a closely related compilation unit.

How do we divide up the declarations and definitions that make up a program into modules?

I usually focus on the headers first, then the compilation units. But in practice this is an iterative process, in which I make an initial division of the headers, look at what that does to the compilation units, modify the headers, look at what that changes the compilation units, etc.

Dividing up the headers:

  1. Start by identifying declarations of types, variables & constants, and functions that relate to a common “theme”.

    Often the theme is “here’s a structured data type and all of the functions that manipulate it”.

  2. Create a module for each such “theme”. Begin by collecting the declarations related to that theme into a single header file.

  3. Pair up compilation units with the header files

    • Usually these will have matching names. If we had decided to have a “time” module with a header named time.h, I would add a compilation unit time.cpp.

    • The compilation unit provide definitions for each declaration in the header that is it paired with.

  4. Now look for things that can be improved, e.g.,

    • Are there declarations in a header that are only used from within its own compilation unit? Hide them within the compilation unit.

    • Does a compilation unit try to use a symbol from another module that isn’t in a header? Move the symbol declaration into the header?

    • Do you have circular dependencies where header file 1 #includes header file 2 that #includes header file 1? Try to simplify by moving things around of maybe dividing one of those modules into two pieces, only one of which is needed by the other.

Dividing a program into modules is something of an art, and takes practice. Experience in reading other people’s code and seeing how they divided things up is useful.

In CS250, you will be introduced to the idea of an “abstract data type”, which is a powerful organizing principle for achieving well-designed modules.