我该如何解释这个LNK2005?

时间:2013-03-13 12:16:59

标签: c++ linker linker-errors

所以,有人来找我的项目无法链接错误LNK2005:已在对象中定义的符号(使用Visual Studio 2010)。在这种情况下,我知道 是错误的(因此可以指出它们是正确的解决方案),但我不知道为什么这在某个级别上是错误的关于它的一个很好的解释(以防止它再次发生)。

// something.h
#ifndef _SOMETHING_H
#define _SOMETHING_H
int myCoolFunction();

int myAwesomeFunction() // Note implementing function in header
{
    return 3;
}
#endif

-

// something.cpp
#include "something.h"
int myCoolFunction()
{
    return 4;
}

-

// main.cpp
#include <iostream>
#include "something.h"

int main()
{
    std::cout << myAwesomeFunction() << std::endl;
}

链接失败,并通过将myAwesomeFunction()放入.cpp并在.h中留下声明来修复。

我对链接器工作原理的理解几乎来自here。据我了解,我们提供了一个地方所需的符号。

我抬起MSDN article on LNK2005,这与我期望链接器表现的方式相符(不止一次提供符号 - &gt;链接器混淆),但似乎不包括这种情况(这意味着我'我不理解关于链接的明显事实。

Google和StackOverflow与不包括#ifndef#pragma once的人产生问题(导致提供符号的多个声明)

A related question I found on this site有同样的问题,但答案并没有解释为什么我们正在充分理解这个问题达到我的理解水平。

我有问题,我知道解决方案,但我不知道为什么我的解决方案有效

3 个答案:

答案 0 :(得分:3)

在典型的C ++项目中,您可以单独编译每个实现(或.cpp)文件 - 通常不会直接将标头(或.h)文件传递给编译器。在执行所有预处理和包含之后,这些文件中的每一个都成为翻译单元。所以在你给出的例子中,有两个翻译单元看起来像这样:

  • main.cpp翻译单位:

    // Contents of <iostream> header here
    
    int myCoolFunction();
    
    int myAwesomeFunction() // Note implementing function in header
    {
        return 3;
    }
    
    int main()
    {
        std::cout << myAwesomeFunction() << std::endl;
    }
    
  • something.cpp翻译单位:

    int myCoolFunction();
    
    int myAwesomeFunction() // Note implementing function in header
    {
        return 3;
    }
    
    int myCoolFunction()
    {
        return 4;
    }
    

请注意,这两个翻译单元都包含重复内容,因为它们都包含something.h。如您所见,上述翻译单元中只有一个包含myCoolFunction的定义。非常好!但是,它们包含myAwesomeFunction的定义。那太糟糕了!

单独编译翻译单元后,将它们链接起来形成最终程序。关于翻译单元的多个声明有一些规则。其中一条规则是(§3.2/ 4):

  

每个程序应该只包含该程序中使用的每个非内联函数或变量的一个定义;无需诊断。

您的程序中有myAwesomeFunction的多个定义,因此违反了规则。这就是您的代码无法正确链接的原因。

您可以从链接器的角度来考虑它。编译完这两个翻译单元后,您有两个目标文件。链接器的工作是将目标文件连接在一起以形成最终的可执行文件。因此,它在myAwesomeFunction中看到对main的调用,并尝试在其中一个目标文件中查找相应的函数定义。但是,有两个定义。链接器不知道使用哪一个,所以它只是放弃了。

现在让我们看看如果您在myAwesomeFunction中定义something.cpp,翻译单位会是什么样子:

  • 修正main.cpp个翻译单位:

    // Contents of <iostream> header here
    
    int myCoolFunction();
    
    int myAwesomeFunction();
    
    int main()
    {
        std::cout << myAwesomeFunction() << std::endl;
    }
    
  • 修正something.cpp个翻译单位:

    int myCoolFunction();
    
    int myAwesomeFunction();
    
    int myCoolFunction()
    {
        return 4;
    }
    
    int myAwesomeFunction()
    {
        return 3;
    }
    

现在它很完美。现在整个程序中只有myAwesomeFunction的一个定义。当链接器在myAwesomeFunction中看到对main的调用时,它知道完全应该将它链接到哪个函数定义。

答案 1 :(得分:1)

链接器只是让你知道你打破了一个定义规则。这是一个基本的,记录良好的C ++规则 - 它不能通过使用包含保护或#pragma once指令来解决,但是,在自由函数的情况下,通过标记它inline或移动实现到源文件。

当在标头中实现非内联方法时,包含该标头的所有翻译单元将定义它。当相应的.obj文件链接在一起时,链接器会检测到多次导出(和定义)相同的符号,并抱怨。

将实现移至cpp文件会有效地将您的初始定义转换为声明

答案 2 :(得分:-2)

myAwesomeFunction在两个源文件中定义:something.cppmain.cpp。将其实现移动到源文件之一,或将此函数声明为静态。