Multiple definitions error in C++

2019-09-16 06:49发布

问题:

I'm writing a C++ program where each file has it's own set of global variable declarations. Most of these files make use of global variables that were defined in the other files using extern.

Here's an example similar to my program:

Main.cpp

#include "stdafx.h"
#include <iostream>
#include "Other_File.cpp"

int var1;
int var2;

int main()
{
    var1 = 1;
    var2 = 2;
    otherFunction();
    var4 = 4; // From Other_File.cpp

    std::cout << var1 << " " << var2 << " " << var3 << " " << var4 << std::endl;

    return(0);
}

Other_File.cpp

extern int var1;
extern int var2;

int var3;
int var4;

void otherFunction()
{
     var3 = var1 + var2;
     var4 = 0;
}

When I build this code in Visual Studio (Windows), everything runs fine and the output is correct. But when I attempt to build using g++ on Linux I receive the following error:

g++ -o Testing Testing.o Other_File.o Other_File.o:(.bss+0x0): multiple definition of var3' Testing.o:(.bss+0x0): first defined here Other_File.o:(.bss+0x4): multiple definition ofvar4' Testing.o:(.bss+0x4): first defined here Other_File.o: In function otherFunction()': Other_File.cpp:(.text+0x0): multiple definition of otherFunction()' Testing.o:Testing.cpp:(.text+0x0): first defined here collect2: ld returned 1 exit status make: *** [Testing] Error 1

Is this because I'm "including" the other file in my main file?

If not what's the issue with my code?

Edit: This is the content of my Makefile for g++:

Testing: Testing.o Other_File.o
    g++ -o Testing Testing.o Other_File.o

Testing.o: Testing.cpp
    g++ -c -std=c++0x Testing.cpp

Other_File.o: Other_File.cpp
    g++ -c -std=c++0x Other_File.cpp

clean:
    rm *.o Calculator

回答1:

Don't #include a source file into another source file. There times and places when that is okay, but only in like less than 0.001% of all programs is that needed.

What you should do is create a header file which contains declarations of the things needed in both source files.

Then your code would look like this:

  1. main.cpp source file

    #include "stdafx.h"
    #include <iostream>
    #include "Other_File.h"  // Note inclusion of header file here
    
    int var1;
    int var2;
    
    int main()
    {
        var1 = 1;
        var2 = 2;
        otherFunction();
        var4 = 4; // From Other_File.cpp
    
        std::cout << var1 << " " << var2 << " " << var3 << " " << var4 << std::endl;
    }
    
  2. other_file.cpp source file, just like you have it now

  3. other_file.h header file, a new file

    #pragma once
    
    // Declare the variables, so the compiler knows they exist somewhere
    extern int var3;
    extern int var4;
    
    // Forward declaration of the function prototype
    void otherFunction();
    

Both source files would then be compiled separately and linked together to form the final executable. This linking step is where your build fails. It will notice that the variables defined in other_source.cpp are defined in the object file created from that source file, but since you include it into the main.cpp source file then the object file created from that source file as well.

This is why you need to learn about translation units, which is what the compiler actually see. A C++ source file goes through many phases of translation, each one doing its own special part. Roughly a translation unit is a single source file with all the headers included.

This is also a good reason to learn what the preprocessor #include directive does. It basically inserts the included file, as is, into the source file being preprocessed. Where the #include directive was, after preprocessing it will be the contents of the included file, and that is what the compiler will see.



回答2:

An include effectively pastes the included file into the including file before the compiler starts working, so what this looks like to the compiler is:

#include "stdafx.h"
#include <iostream>
// #include "Other_File.cpp" sub in other file here
extern int var1;
extern int var2;

int var3;
int var4;

void otherFunction()
{
     var3 = var1 + var2;
     var4 = 0;
}


int var1;
int var2;

int main()
{
    var1 = 1;
    var2 = 2;
    otherFunction();
    var4 = 4; // From Other_File.cpp

    std::cout << var1 << " " << var2 << " " << var3 << " " << var4 << std::endl;

    return(0);
}

By itself this isn't a problem. It's funny looking, but it's not a problem. There are no duplicated variables.

extern int var1;

tells the compiler that somewhere else the there exists var1. It will be found and linked later. Carry on. The linker will complain if you lied. In this case, there it is about 10 lines down.

The problem comes when Other_File.cpp is compiled and linked with Main.cpp. The linker finds a full definition of var3 and var4 in both Main.o and Other_File.o

You can exclude Other_File.cpp from the build, likely what Visual Studio did for you, and all will be good except for a serious violation of convention: Never include a .cpp file.

Why? Because cpp files, by convention, define stuff and make them real. You can botch a .h file just as badly by defining a variable or function in the header. eg:

#ifndef BADHEADER_H_
#define BADHEADER_H_


int fubar; // don't do this
/* do this instead:
extern int fubar;
and place int fubar; into the most logical cpp file
*/


#endif /* BADHEADER_H_ */

Everyone who includes badheader.h now has a variable named fubar whether they want it or not and when the linker comes along to assemble the program, which fubar is the real fubar? All of them. Whoops. All the poor linker can do is spit out an error and wait for you to fix the ambiguity.

At the end of the day you can #include anything. It won't compile, but you could include a Word doc. You can even perform little tricks like

int array[] =
{
#include "filtercoefs.h"
};

where array.h is simply

1,2,3,4,5

Useful when you have something like Matlab spitting out filter coefficients, but prefer sticking to the convention to prevent confusion among your fellow coders.

Anyway, C++ lets you do all sorts of stuff. Most of them you better think over a few times before doing them.