我该怎么解释这个LNK2005

How do I explain this LNK2005?

本文关键字:LNK2005 解释      更新时间:2023-10-16

所以,有人带着一个项目来找我,这个项目链接失败,错误是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中留下声明来修复。

我对链接器如何工作的理解基本上来自这里。根据我的理解,我们提供的是一个地方需要的符号。

我在LNK2005上查找了MSDN文章,这与我期望链接器的行为相匹配(多次提供符号->链接器混淆),但似乎没有涵盖这种情况(这意味着我不理解链接的一些明显的东西)。

Google和StackOverflow在不包含#ifndef#pragma once的情况下产生问题(这会导致对所提供的符号进行多次声明)

我在这个网站上发现了一个相关的问题,有同样的问题,但答案并没有解释为什么我们得到了这个问题充分到我的理解水平。

我有一个问题,我知道解决方案,但我不知道为什么我的解决方案工作

在典型的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的定义,所以你违反了规则。这就是为什么你的代码不能正确链接。

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

现在让我们看看如果你在something.cpp:

中定义myAwesomeFunction的翻译单位是什么样子的
  • 固定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的定义。当链接器在main中看到对myAwesomeFunction的调用时,它确切地知道应该链接到哪个函数定义。

链接器只是让您知道您违反了一个定义规则。这是一个基本的、有充分文档的c++规则——它不能通过使用include守卫或#pragma once指令来解决,但是,在一个自由函数的情况下,通过将其标记为inline或将其实现移动到源文件来解决。

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

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

myAwesomeFunction在两个源文件中定义:something.cppmain.cpp

将其实现移动到源文件之一,或者将此函数声明为静态。