Dealing with multiple files

Programming Workshop 2 (CSCI 1061U)

Faisal Qureshi

Faculty of Science, UOIT

http://vclab.science.uoit.ca


The story so far

Given a C++ program stored in a file main.cpp shown below

#include <iostream>
using namesapce std;

int main(int argc, char** argv) 
{
    cout << “Hello world\n”;
}

We create the executable as follows

> g++ main.cpp -o helloworld

It is worth remembering that the above statement first compiles the file main.cpp and then links it agains standard libraries. Afterall we never defined cout. Did we? Clearly, cout is implemented elsewhere.

But what if our program consists of multiple files. Any "interesting" program mostly likely would consist of more than one file. Before we proceed lets convince ourselves that there is benefit to spread the program across multiple files.

Consider, for example, the case when multiple developers work together on a single program. It is easier if each developer has sole access to a file. This prevents one developer overwriting on the work done by another developer. The ability to spread a program across multiple files is extremely useful when multiple developers are working on the same piece of code.

Often times we need to use code written by someone else in our program. How would we do it if we are always restricted to a simple file?

Multi-file programs are everywhere, and we need ways to manage such programs.

Lets consider a simple example. Here we have a program that consists of two files as shown below

main.cpp

#include <iostream>

int main(int argc, char** argv)
{
    say_greetings();
    return 0;
}

greetings.cpp

#include <iostream>

void say_greetings()
{
    std::cout << "Hello world." << std::endl;
}

Lets try our previous approach g++ main.cpp -o helloworld.

> g++ main.cpp -o helloworld
main.cpp:5:2: error: use of undeclared identifier 'say_greetings'
        say_greetings();
        ^
1 error generated.

This didn't work. The linker was not be able to find the symbol say_greetings,since it is not declared or defined in file main.cpp. How do we then fix this issue? Function say_greetings isn't even defined in this file.

First compile, then link

We can fix it by fist compiling the two .cpp files into object files and then linking the two object files to create the final executable. We will use -c switch to tell the g++ that we only want to compile the C++ files.

First, lets try to compile greetings.cpp file.

> g++ -c say_greetings.cpp

This will contain a file say_greetings.o. Do confirm that such a file is indeed created.

Now lets try to compile main.cpp

> g++ -c main.cpp
main.cpp:5:2: error: use of undeclared identifier 'say_greetings'
        say_greetings();
        ^
1 error generated.

Oops. This doesn't fix the problem. Is there no end to our troubles?

And here come the header files

What we need is to tell the compiler that function say_greetings is defined in some other file. An easy way to do it is to create a header file as follows

say_greetings.h

void say_greetings();

Now we can "include" this file in the main.cpp file as follows:

#include <iostream>
#include "say_greetings.h"

int main(int argc, char** argv)
{
    say_greetings();
    return 0;
}

Now lets try to compile main.cpp

> g++ -c main.cpp

It works now. Confirm that you now have main.o as well.

Okay, so now we can link the two object files -- main.o and say_greetings.o and create the executable.

> g++ main.o say_greetings.o -o helloword
> ./main
Hello world.

It works now! Notice that we started with two files main.cpp and say_greetings.cpp, and we created a header file say_greetings.h. We will include this header file anywhere we need to use the function say_greetings defined in say_greetings.cpp file.

How to write a header file

In larger programs, with many cpp and h files, it is often preferable to use preprocessor directives to ensure that the header file contents aren't included twice by mistake. We can do so as follows.

#ifndef __say_greetings_h__
#define __say_greetings_h__

void say_greetings();

#endif

Essentially, when this file is encountered for the first time during any compiler pass, __say_greetings_h__ is defined. When this file is encountered the second or subsequent times, its contents are ignored since __say_greetings_h__ is already defined.

A more involved example

Consider the following C++ program that comprises 4 files:

Source files

The first file contains the main() function. Each CPP program must have only one main() function that serves as the entry-point into the program.

This file uses Arr struct two functions prn_arr() and ave_arr, which are not defined in this file.

Struct Arr is defined in Arr.h. We use #include to include Arr.h file into main.cpp.

Line extern void prn_arr(struct Arr& a); declares that function prn_arr is defined somewhere else. Notice how we avoided creating a header file just for the sake of a single function.

Line extern double ave_arr(struct Arr& a); declares that function ave_arr is defined somewhere else. Notice how we avoided creating a header file just for the sake of a single function.

main.cpp

#include <iostream>
#include "Arr.h"
using namespace std;


extern void prn_arr(struct Arr& a); 
extern double ave_arr(struct Arr& a);

int main()
{
  int i = 0;
  Arr a;
  // double arr[10];
  // int sz;

  do {
    double tmp;
    cin >> tmp;

    if (tmp < 0) {
      a.sz = i;
      break;
    }

    a.arr[i] = tmp;
    i = i + 1;
  } while(true);

  prn_arr(a);
  cout << ave_arr(a) << endl;

  return 0;

Function prn_arr is defined in this file.

out.cpp

#include <iostream>
#include "Arr.h"
using namespace std;


void prn_arr(struct Arr& a)
{
  for (int i=0; i<a.sz; i++) {
    cout << a.arr[i] << ' ';
  }
  cout << endl;
}

The second file defines ave_arr.

math.cpp

#include <iostream>
#include "Arr.h"
using namespace std;


double ave_arr(struct Arr& a)
{
  double sum=0.;

  for (int i=0; i<a.sz; i++) {
    sum = sum + a.arr[i];
  }

  return sum / (double) a.sz;
}

Struct Arr is defined in the following header file.

Arr.h

struct Arr
{
  double arr[10];
  int sz;
};

Creating the executable

Use g++ with -c flag to compile cpp files to object files as follows:

$ g++ -c main.cpp
$ g++ -c out.cpp
$ g++ -c math.cpp

Now link the object files to create the executable ar.

$ g++ main.o out.o math.o -o ar

Now you can execute the program as follows:

$ ./ar 

Note that we never compiled Arr.h file. A header file is only compiled when encountered during a compile pass for a cpp file that include this file.