First Next Previous Last Glossary About

Compilation units, include files and other issues


Discipline with include files

It's common to find that with moderately complex programs consisting of a number of source files, the same header file is included several times during the same compilation. The standard include files are a good example of this. Here is another small (and rather forced example) that demonstrates this:

//header data3.h
typedef struct
{ char x;
} s3;
//header data1.h
#include "data3.h"
typedef struct
{ char x;
  s3   j;
} s1;
//header data2.h
#include "data3.h"
typedef struct
{ char x;
  s3   j;
} s2;
#include <iostream>
#include "data1.h"
#include "data2.h"

int main ()
{ s1 a;
  s2 b;
  return 1;
}

You can see we are using three structs s1, s2, s3 and that s1 and s2 both have a member of type s3. Consequently data1.h and data2.h both need to include data3.h.

In file included from data2.h:1,
                 from data.cc:5:
data3.h:7: conflicting types for `typedef struct s3 s3'
data3.h:7: previous declaration as `typedef struct s3 s3'

When we build this program we get an error because both data1.h and data2.h include the file data3.h. In effect each of data1 and data2 is trying to declare a data type s3.


#ifndef DATA3_H
#define DATA3_H

typedef struct
{ char x;
} s3;

#endif

The solution to this problem is shown here. We wrap the header in some directives and let the preprocessor handle things for us. When our program is compiled data1.h is included. CPP reads data3.h as directed by data1.h and determines that the constant DATA3_H has not been defined. That is what the directive #ifndef means - If Not Defined. Since, at this stage DATA3_H is not defined it is defined by the next directive and this is followed by the declaration for s3. The #endif directive ends thf #ifndef directive.

Next data2.h is included. It too is directed to include data3.h but now the #ifndef directive determines that DATA3_H is defined and does not do any further work with data3.h.

You should adopt this discipline for all your include files. Start every include file with a #ifndef and end it with an #endif.

The convention is to use the include file name as a define with underscore replacing the . (dot) just as I have done with this example.


Return to top of page

Compilation units

A compilation unit is the collection of files that go to make up an object file, ie all the source files and all the header files and other resources.

Multiple definitions of variables

Figure 1 demonstrates what can go wrong when we aren't careful enough with the definition of what goes into a compilation unit.

We have a header file which has the usual includes but it also contains a declaration for a variable. Now you know variables take up memory. Since this variable is in the include file it is global.

Both of the source files use header.h and when they are compiled the compiler will set aside memory for x inside the object file. The files will compile OK since there is no problem with allocating x's memory at this time.

The problem occurs at link time. The linker links the two object files and discovers that they have both defined an area of memory called x. The link will fail with a "multiple definition" error.


Avoiding multiple definition of variables

To fix this kind of problem we have to resolve the issue of memory allocation for common variables at link time.

We do this by taking the definition of x out of the header file and putting into its own source file. This means that memory for x is only allocated when my_i.cc is compiled.

The question is "how do one.cc and two.cc make use of x?" and the answer is they declare an external variable.

The lesson on scope and classes of storage introduced the notion of extern. When we use the extern storage specifier we ensure that memory for a variable is only allocated once.

The question of where the memory for x comes from is resolved at link time.


Return to top of page


First Next Previous Last Glossary About


Copyright © 1999 - 2001 David Beech