Think of it this way: each .cpp
file is preprocessed and then compiled completely separately from the other files.
So let's first preprocess main.cpp
. This involves looking at all the lines beginning with #
. The file main.cpp
only has #include
lines, which simply copy the contents of the file they're including. I'm going to represent the contents of stdafx.h
and iostream
with a comment, but I'll actually copy the contents of add.h
in:
// Contents of stdafx.h
// Contents of iostream
#ifndef ADD_H
#define ADD_H
int add(int x, int y);
#endif
int _tmain(int argc, _TCHAR* argv[]) {
std::cout << "3 + 4 = " << add(3, 4) << std::endl;
system("pause");
return 0;
}
See now that the contents of add.h
have been brought into main.cpp
? And it so happens that this has brought in some more preprocessor directives, so we'll need to do what they say. The first checks if ADD_H
is not defined yet (in this file), which it is not, and so leaves everything up until the #endif
:
// Contents of stdafx.h
// Contents of iostream
#define ADD_H
int add(int x, int y);
int _tmain(int argc, _TCHAR* argv[]) {
std::cout << "3 + 4 = " << add(3, 4) << std::endl;
system("pause");
return 0;
}
Now the remaining preprocessor directive defines ADD_H
and we're left with the final translation unit:
// Contents of stdafx.h
// Contents of iostream
int add(int x, int y);
int _tmain(int argc, _TCHAR* argv[]) {
std::cout << "3 + 4 = " << add(3, 4) << std::endl;
system("pause");
return 0;
}
Now this file can be compiled. If you call a function like add
, the compiler only needs to be able to see the declaration of that function for it to compile successfully. It is expected that the function will be defined in some other translation unit.
So now let's look at preprocessing add.cpp
. In fact, add.cpp
doesn't have any preprocessing directives, so nothing needs to happen. Typically, you would #include "add.h"
, but your program will still compile if you don't. So after preprocessing we still have:
int add(int x, int y) {
return x + y;
}
This then gets compiled and we now have a definition of the add
function.
After all .cpp
files have been compiled, they are then linked. The linker is responsible for seeing that the compiled main.cpp
uses the function add
and so looks for its definition. It finds the definition in the compiled add.cpp
and links them together.
You may then wonder why we have include guards at all. It seemed to be pretty worthless in this example. That's right, in this example it didn't really have any use. Include guards are there to prevent the same header being included twice in a single file. This can easily happen when you have a more complex project structure. However, let's look at an unrealistic example where main.cpp
includes add.h
twice:
#include "stdafx.h"
#include <iostream>
#include "add.h"
#include "add.h"
int _tmain(int argc, _TCHAR* argv[]) {
std::cout << "3 + 4 = " << add(3, 4) << std::endl;
system("pause");
return 0;
}
Preprocessing this gives you:
// Contents of stdafx.h
// Contents of iostream
#ifndef ADD_H
#define ADD_H
int add(int x, int y);
#endif
#ifndef ADD_H
#define ADD_H
int add(int x, int y);
#endif
int _tmain(int argc, _TCHAR* argv[]) {
std::cout << "3 + 4 = " << add(3, 4) << std::endl;
system("pause");
return 0;
}
The first #ifndef
will be process, it will see that ADD_H
is not yet defined, and everything up until the #endif
will remain. This then defines ADD_H
.
Then the second #ifndef
is processed, but at this point ADD_H
has been defined, so everything up until the #endif
will be discarded.
This is very important because having multiple definitions of a function (and many other things) will give you an error.