preprocessor complilation failure

14 Oct 2011

If you declare a variable in #ifndef in a header file and include that header in multiple locations, the compiler will fail with the error: multiply defined variable.

15 Oct 2011

Hi Mama,

If you are doing what I think you are, then this actually sounds like expected behaviour. I suspect you are not telling the compiler to do quite what you think you are :)

My guess is you have something that distills down to:

foobar.h

#ifndef FOOBAR_H
#define FOOBAR_H
int x;
#endif

foo.cpp

#include "foobar.h"

bar.cpp

#include "foobar.h"

Yet you get:

Error

Symbol x multiply defined (by foo.o and bar.o)

So why should this give an error? Surely the #ifndef means x only gets included once? It has always worked before! Well, now you have a choice...

You can take the blue pill. You've seen these #ifndef, #includes and header files used like this before. You know it should work. Stupid compiler. So just fiddle a bit until the compiler stops complaining. Continue a happy life with a blissful illusion of how a compiler goes about its job. You can ignore the occasional glitch.

Or you can take the red pill. You sense the compiler isn't doing what you thought it should for some reason. In fact, you worry it never has. But do you really want to know what all these things actually mean? You can't go back.

Take the red pill:

First, an explanation of how C compilers and the C pre-processor actually work, and how multiple definition problems can occur, which will hopefully start to make it more clear:

A really obvious case; you define the variable twice in the same source (.c/.cpp) file!

main.cpp

int x;
int x;
int main() {}

Error

variable "x" has already been defined" in file "/main.cpp

Hopefully, this one is pretty obvious! The compiler catches this when it looks at the source file.

The next common one would be based on two source files having the same variable:

main.cpp

int x;
int main() {}

other.cpp

int x;

Error

Symbol x multiply defined (by main.o and other.o)

In this case, the error is slightly different. Notice it doesn't refer to a particular source file this time; in fact, it refers to two different object files. To understand this, lets explain a little more of what goes on when you compile a program; the compiler goes through two stages:

  • Independently translate each .c/.cpp source file to a corresponding .o assembly object file (compiling)
  • Combine all the different .o object files in to one .bin binary image (linking)

So first the compiler compiles main.cpp to main.o, and that is fine. Then it tries to compile other.cpp to other.o, and because the compilation is independent, that is also fine; it doesn't know anything about the "x" in the other source file. But then when the linker tries to combine all the object files together to make the final binary image, it falls over, because it has two variables named "x" which is not allowed.

So these two examples show the underlying reason for multiply defined symbol errors, the difference between a compile time and link time errors, and how to distinguish them from the error message.

In these examples, the problem is fairly obvious, and it is clear why the computer says no. Where people often become unstuck is when the C pre-processor and header files join the party.

In our case, we had the #ifndef's to ensure these errors didn't happen. So why didn't that work?

Well, as in this case, it is simply a slight misunderstanding of how compilers, header files and the c pre-processor actually work. Like a lot of things, it is simple when you know how, and therefore why.

When compiling a source file, it is first passed to the c pre-processor to manipulate the file in useful ways before it is compiled; this is to do things like define variables used later in that file (#define), conditionally skipping/processing bits of the file (#ifdef, #ifndef), and including other files (#include). You can think of #include as just copying the referenced file and pasting it in the place the #include appears. For more details on the C pre-processor, see:

So lets look at that code again:

foobar.h

#ifndef FOOBAR_H
#define FOOBAR_H
int x;
#endif

foo.cpp

#include "foobar.h"

bar.cpp

#include "foobar.h"

The mistake become clear if you think about two important points we've covered:

  1. Each .c/.cpp file is being compiled independently
  2. The preprocessor is simply modifying that single .c/.cpp file before passing it to the compiler

So when the compiler compiles foo.cpp, it includes the contents of foobar.h in it because of the include. And FOOBAR_H hasn't been defined, so it is defined and x is included. But when the compiler now independently goes and compiles bar.cpp, exactly the same thing happens. So both foo.o an bar.o contain x. And hence we get the link error.

I'll leave it there for now, but you could also take a look at the "Modular programming" Course Notes Slides, especially from slide 19.

Hope that helps!

Simon

19 Oct 2011

Yes, I'm very happy with the explanation, but now I'm confused as to why I ever used #ifndef to begin with.

26 Apr 2012

That's an interesting point. I am curious too. if every .cpp is compiled independently, then these stupid undreadable #ifndef are completely useless... Maybe only if one header includes another header that is already included in another header... Then it is useful not to include same stuff again...

26 Apr 2012

Including headers from headers is exactly what this is designed to mitigate, even using a simple string class in a class definition would get complicated without ifndefs around the strings header file, as you could easily be using 2 classes using strings in one source file