C++: Understanding Header Files & Header Guards wi

2019-08-07 18:22发布

问题:

I can't get my head around headers and header guards. I've read other questions and their answers but I still can't make this work in Visual Studio 2013:

main.cpp

#include "stdafx.h"
#include <iostream>
#include "add.h"

int _tmain(int argc, _TCHAR* argv[]) {
    std::cout << "3 + 4 = " << add(3, 4) << std::endl;
    system("pause");
    return 0;
}

add.cpp

#include "stdafx.h" //ADDED LATER; NOW WORKING (AND SEE QUESTION 2 BELOW)
#include "add.h" //ADDED LATER; NOR WORKING (AND SEE QUESTION 2 BELOW)
int add(int x, int y) {
    return x + y;
}

add.h

#ifndef ADD_H
#define ADD_H
int add(int x, int y);
#endif

When I compile, the console window flashes on-screen then disappears. The Error List contains:

Error 1 error LNK2019: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z) referenced in function _wmain c:\Users\Danny\documents\visual studio 2013\Projects\Addition Program\main\main.obj main

Error 2 error LNK1120: 1 unresolved externals c:\users\danny\documents\visual studio 2013\Projects\Addition Program\Debug\main.exe main


1. How do headers and header guards work? I see how by #including add.h, it makes main.cpp aware of the declaration of add(int x, int y), but then how does it find its definition?


2. What have I got wrong in my code?

My code is compiling now. The reason my code wasn't compiling was because I had been going File > New > File... to add files to my project, as opposed to adding them through the Source Files and Header Files sections of the Solution Explorer in Visual Studio. I also needed to add #include "stdafx.h to the add.cpp file.

回答1:

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.



回答2:

The IDE compiles each .cpp file in turn to generate an object file (the machine code) for that particular file.

With all this done it writes the bits up to form an executable. the IDE knows what needs to be linked with what.

This is a bit simplistic anwser