Skip to content

Separate Compilation

To create truly reusable functions, their implementations need to be written in a separate file that does not contain a main function. This approach, known as separate compilation, allows you to organize your project into multiple files, making it easier to manage, test, and reuse your code. By combining a makefile, header files, source files, and a driver, you can build modular and scalable C++ projects.

A header file (.h) is used to declare function prototypes, class definitions, and other shared components, allowing them to be included in multiple C++ files. By placing your function prototypes in a header file, you enable other source files to access and use those functions without redefining them.

  • Reusable Prototypes: Placing function prototypes in a header file makes them accessible to multiple source files within a project.

  • Header Guards: To prevent multiple inclusions of the same header file, use header guards. These ensure that the contents of the header file are included only once during compilation:

program.h
// Copyright 2024 CSCE240
#ifndef PROJECT_PATH_FILENAME_H_
#define PROJECT_PATH_FILENAME_H_
// Function prototypes here
int aFunction(int, int);
void anotherFunction();
#endif

A source file (.cc) contains the actual implementation of your functions and includes the corresponding header file for the function prototypes. This structure allows for clean separation of declarations and implementations, enhancing modularity and reusability in your code.

  • Including the Header File: The source file begins by including the header file that declares the function prototypes. This ensures that the implementation matches the declarations.
source.cc
// Copyright 2024 CSCE240
#include "header.h"
int aFunction(int x, int y) {
// Do something
}
void anotherFunction() {
// Do something
}
  • Compiling the Source File: Use the g++ -c command to compile the source file into an object file (.o). For example, the following would generate a source.o file:
Terminal window
g++ -Wall -std=c++17 -c source.cc
  • Linking Object Files: The object code can be linked with other object files and a driver file to create an executable. For example:
Terminal window
g++ -Wall -std=c++17 source.o driver.o -o executable

Lets take the previous Calculator program from the Writing and Using page and properly separate it into different files.

Create a file called calculator.h and add the function prototypes:

calculator.h
// Copyright 2024 CSCE240
#ifndef CALCULATOR_H_
#define CALCULATOR_H_
void Greet();
void FrontEnd();
int Add(int x, int y);
int Subtract(int x, int y);
int Multiply(int x, int y);
double Divide(double num, double denom);
int Square(int x);
#endif

Checking the file with cpplint will throw the following error:

Terminal window
cpplint calculator.h
calculator.h:3: #ifndef header guard has wrong style, please use: FILE_PATH [build/header_guard] [5]
calculator.h:14: #endif line should be "#endif // FILE_PATH" [build/header_guard] [5]

Now create a file called calculator.cc and add the implementation for each function:

calculator.cc
// Copyright 2024 CSCE2024
#include "calculator.h"
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
void Greet() {
cout << "------------------------------------" << endl;
cout << "Welcome to the CSCE240 Calculator!!!" << endl;
cout << "------------------------------------\n" << endl;
}
void FrontEnd() {
cout << "Enter 1: To Add" << endl;
cout << "Enter 2: To Subtract" << endl;
cout << "Enter 3: To Multiply" << endl;
cout << "Enter 4: To Divide" << endl;
cout << "Enter 5: To Square" << endl;
cout << "Enter 0: To Quit" << endl;
}
int Add(int x, int y) { return x + y; }
int Subtract(int x, int y) { return x - y; }
int Multiply(int x, int y) { return x * y; }
double Divide(double num, double denom) {
if (num == 0 || denom == 0) {
return 0;
} else {
return num / denom;
}
}
int Square(int x) { return x * x; }

Again, running cpplint will throw the following error:

Terminal window
cpplint calculator.cc
calculator.cc:3: Include the directory when naming header files [build/include_subdir] [4]

The Driver will contain our main function so create a file called driver.cc and add what your program does:

driver.cc
// Copyright 2024 CSCE240
#include <iostream>
#include "calculator.h"
using std::cin;
using std::cout;
using std::endl;
int main() {
Greet();
bool run_program = true;
while (run_program) {
FrontEnd();
int input;
cin >> input;
switch (input) {
case 0:
run_program = false;
break;
case 1: // Add
cout << "Enter the first integer to add: " << endl;
int first_add;
cin >> first_add;
cout << "Enter the second integer to add: " << endl;
int second_add;
cin >> second_add;
cout << first_add << " + " << second_add << " = "
<< Add(first_add, second_add) << "\n"
<< endl;
break;
case 2: // Subtract
cout << "Enter the first integer to subtract: " << endl;
int first_sub;
cin >> first_sub;
cout << "Enter the second integer to subtract: " << endl;
int second_sub;
cin >> second_sub;
cout << first_sub << " - " << second_sub << " = "
<< Subtract(first_sub, second_sub) << "\n"
<< endl;
break;
case 3: // Multiply
cout << "Enter the first integer to multiply: " << endl;
int first_mul;
cin >> first_mul;
cout << "Enter the second integer to multiply: " << endl;
int second_mul;
cin >> second_mul;
cout << first_mul << " * " << second_mul << " = "
<< Multiply(first_mul, second_mul) << "\n"
<< endl;
break;
case 4: // Divide
cout << "Enter the numerator: " << endl;
int num;
cin >> num;
cout << "Enter the denominator: " << endl;
int denom;
cin >> denom;
cout << num << " / " << denom << " = " << Divide(num, denom) << "\n"
<< endl;
break;
case 5: // Square
cout << "Enter the integer you would like to square: " << endl;
int square;
cin >> square;
cout << square << "^2 = " << Square(square) << "\n" << endl;
break;
default:
cout << "Please enter an integer for the available options\n" << endl;
}
}
cout << "Calculator powering off..." << endl;
return 0;
}

The final file to create is a makefile. Not using one becomes incredibly tedious as we would have to compile and link various files. Add the following code to your makefile:

driver.cc
# Variables
compiler = g++
flags = -Wall -std=c++17
compile = $(compiler) $(flags) -c
link = $(compiler) $(flags)
# Compile
calculator.o : calculator.cc calculator.h
$(compile) $<
# Compile
driver.o : driver.cc calculator.h
$(compile) $<
# Link and Execute
driver : driver.o calculator.o
$(link) $^
./a.out
# Clean Folder
clean :
rm *.o hello

Now that we created a makefile we can compile, link, and run our calculator program. Though the makefile shows many commands, we only have to run the driver script to accomplish everything!

Terminal window
make driver
g++ -Wall -std=c++17 -c driver.cc
g++ -Wall -std=c++17 -c calculator.cc
g++ -Wall -std=c++17 driver.o calculator.o
./a.out
------------------------------------
Welcome to the CSCE240 Calculator!!!
------------------------------------
Enter 1: To Add
Enter 2: To Subtract
Enter 3: To Multiply
Enter 4: To Divide
Enter 5: To Square
Enter 0: To Quit
1
Enter the first integer to add:
2
Enter the second integer to add:
2
2 + 2 = 4
Enter 1: To Add
Enter 2: To Subtract
Enter 3: To Multiply
Enter 4: To Divide
Enter 5: To Square
Enter 0: To Quit
0
Calculator powering off...