4

因此,有人带着一个项目来找我,该项目链接失败并出现错误 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 与不包含#ifndefor的人产生问题#pragma once(这导致提供的符号的多个声明)

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

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

4

3 回答 3

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):

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

您在整个程序中有多个定义,myAwesomeFunction因此您违反了规则。这就是为什么您的代码没有正确链接的原因。

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

现在让我们看看如果您在中定义的翻译单元是什么样myAwesomeFunctionsomething.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现在整个程序只有一个定义。当链接器看到对 in 的调用myAwesomeFunctionmain,它确切地知道应该将它链接到哪个函数定义。

于 2013-03-13T12:24:19.053 回答
1

The linker is merely letting you know that you broke the one definition rule. This is a basic, well-documented rule of C++ - it isn't solved by using include guards or #pragma once directives, but, in case of a free function, by marking it inline or moving the implementation to a source file.

When a non-inline method is implemented in a header, all translation units that include that header will define it. When the corresponding .obj files are linked together, the linker detects the same symbol is exported (and defined) multiple times, and complains.

Moving the implementation to a cpp file effectively transforms your initial definition into a declaration.

于 2013-03-13T12:20:17.180 回答
-2

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

于 2013-03-13T12:22:08.777 回答