Recall: The preprocessor transforms your program before the compiler sees it

\#include<iostream>

include is a preprocessor directive

\#define VAR VAL

This sets a preprocessor variable to have value, all occurrences of VAR in your source file get replaced with VAL except occurrences in quoted strings.

\#define MAX 10
int x[MAX];
 
// would be transformed into x[10];

However, this is not used a lot in C++, in fact, almost never used. It is ALWAYS better to just use const.

\#define FLAG
  • This sets the variable FLAG, its value is the empty string
  • Defined constants are useful for conditional compilation
    • What this means is hiding code from the complier that you don’t necessarily want compiled

For example:

\#define SECURITYLEVEL 1
 
# if SECURITYLEVEL == 1
short int
\#elif SECURITYLEVEL == 2
long long int
\#endif

Special case:

\#if 0
 
...
 
\#endif

\#if 0 is never true, so all of the inner text is removed before the compiler sees it, works as a heavy duty “comment out”.

We can also define preprocessor symbols, via the command-line compiler arguments.

Say we have the following incorrect code:

int main() {
  cout << x << endl;
}

Then, to compile it, we:

g++ -DX=15 define.cc -i define
./define
15

x is now set to 15 through the command-line.

\#ifdef

NAME/\#ifdef NAME, true if var/flag has/has not been defined.

int main() {
  \#ifdef DEBUG
  cout << "setting x = 1" << endl;
 
  \#endif
  int x = 1;
  while (x < 100) {
    ++x;
    \#ifdef DEBUG
    cout << "x is now" << endl;
    \#endif
  }
}

Compiling this: g++ loop.cc -o loop, we see no print statements

Now, with:

g++ -DEBUG loop.cc -o
./oop

With the above, we see our prints.


Separate compilation

The point of this is to split the program into composable modules, each which provide:

  • Interface file:
    • type definitions
    • prototypes (declarations)
    • This is the header file, the .h file
  • Implementation:
    • Full definition for every provided function
    • The .cc file

Recall from C

  • declaration: asserts existence
  • definition: full details, allocates space ( for var and functions)

Example

//vec.h (interface file)
 
struct vec {
  int x,y;
}; // definition of the type vec
 
vec operator+(const vec &v1, const vec &v2);
//main.cc (client)
\#include "vec.h"
 
int main() {
  vec v{1,2}
  v = v + v;
}
//vec.cc (implementation file)
 
\#include "vec.h"
vec operator+(const vec &v1, const vec &v2) {
  vec v {v1.x + v2.x, v1.y + v2.y};
  return v;
}
 
// Recall: An entity can be declared as many times as you want,
// but defined only once.

Compiling Separately

NEVER COMPILE **.h files**

To do so, we use;

g++ -c vec.cc  # -c means compile only, do not *link*, so above, only comiple vec.cc
g++ -c main.cc # does not build executable, instead, produces an object file (.o file)
 
g++ vecc.o main.o -o main
# link the object files together to produce executable main

What happens if we change vec.cc, we only need to recompile vec.cc and relink

But, what if we change vec.h? Now, we need to recompile vec.cc and main.cc since both files include vec.h, and then we must relink.

How can we keep track of dependencies, and perform minimal compilation?

Linux tool: make

To use make, we create a makefile (must be named that), that says which files depend on which other files, and how to build your program.

main: main.o vec.o \#target main depends on vec.o and main.o
  g++ main.o vec.o -o main # how to build to get main
 
# the space in front of the g++ MUST be a tab character
 
main.o: main.cc vec.h
  g++ -std=c++14 -c main.cc
 
vec.o: vec.cc vec.h
  g++ -std=c++14 -c vec.cc
main: main.o vec.o \#target main depends on vec.o and main.o
  g++ main.o vec.o -o main # how to build to get main
 
# the space in front of the g++ MUST be a tab character
 
main.o: main.cc vec.h
  g++ -std=c++14 -c main.cc
 
vec.o: vec.cc vec.h
  g++ -std=c++14 -c vec.cc
 
# now with the below, it gets rid of the compiled files, removes everything
.PHONY: clean
 
clean:
  rm \\*.o main
 
# basically, telling make to run this "remove" command for us

Then from cmd-line:

make # builds whole project
 
# - now if we just change vec.cc
 
make # compiles vec.o and relinks
make target \#builds the requested target, wihtout target (as above) builds the first

make just checks time stamps, if a target is older than its dependencies, it must rebuild (can cascade.recurse)

lets generalize with variables

CXX = g++  \#compiler name
CXXFLAGS = -std=c++14 -Wall # compiler flags
OBJECTS = main.o vec.o
EXEC = main
 
${EXEC}: ${OBJECTS}
  ${CXX} ${OBJECTS} -o ${EXEC}
 
main.o: main.cc vec.h
vec.o: vec.h vec.c
 
# can now omit recipes, make assumes that the recipe is ${CXX} ${CXXFLAGS} -c *filename*.cc

Great, but the biggest problem is knowing dependencies

We can get help from g++ with the -MMDflag, which produces compilation dictionaries

now,:

CXXFLAGS = -std=c++14 -Wall -MMD # compiler flags
 
# Using this in the above block of code:
 
g++ -MMD -c vec.cc \#produces vec.o and vec.d
cat vec.d
vec.o: vec.cc vec.h
# then, we can change:
 
CXX = g++  \#compiler name
CXXFLAGS = -std=c++14 -Wall -MMD
OBJECTS = main.o vec.o
EXEC = main
DEPENDS = ${OBJECTS:.o=.d} # so declares everything in main with .o and the end, and makes a .d file
${EXEC}: ${OBJECTS}
  ${CXX} ${OBJECTS} -o ${EXEC}
 
include ${DEPENDS}
 
\#WILL HAVE TO MAKE A "MAKE FILE" IN THE FUTURE FOR THIS COURSE

As project expands, only have to add .o filesto the OBJECTS variable.