链接器如何决定使用哪个实现

how linker decides which implementation use

本文关键字:实现 决定 何决定 链接      更新时间:2023-10-16

假设我们有以下文件:

//foo.h
class Foo
{
public:
    void foo()
    {
        //Great code here
    }    
};
//foo1.cpp
#include "foo.h"
void Foo1()
{
    Foo f1;
    f1.foo();
}
//foo2.cpp
#include "foo.h"
void Foo2()
{
    Foo f2;
    f2.foo();
}

当我将它们分开编译时,它们会生成两个对象:Foo1.o Foo2.o。当我将它们链接在一起时,它们完美地链接在一起。

现在,如果我为两者转储符号表,似乎在两个编译单元中实现了 Foo::foo 函数。

_ZN3Foo3fooEv

现在,链接器如何区分要使用的实现?

Mats Petersson 的回答完全正确,但我会用我自己的话用不同的覆盖范围来旋转它......

当您编译C++代码时,一次编译一个翻译单元...每个翻译单元通常由一个实现文件(例如 .cpp/.cc 或你选择命名它的任何文件(和它包含的头文件组成,编译器会生成一个.o文件。 当编译器看到你的foo.hFoo::foo()的定义时,它会认为它是一个名义上的内联函数,因为函数体出现在类中。 因此,编译器可能会也可能不会在调用点实际内联函数 - 该决定将取决于函数的大小/复杂性以及编译器的启发式和选项。 因此,对于两个翻译单元,Foo::foo可能最终仍会成为.o中单独的外联函数。

由于函数名义上是内联的,编译器需要确保该符号被标记为"弱符号"(确切的术语可能因操作系统/工具链而异 - 这是低于C++标准级别的实现细节( - 请参阅 http://en.wikipedia.org/wiki/Weak_symbol

当链接的对象具有相同的弱符号时,将保留一个副本中的代码,而丢弃其他副本中的代码。 因此,两个.o文件都可以具有该功能(尽管由于类中的定义,它名义上是内联的(,但从.o链接的可执行文件只有一个副本。

由于Foo::foo()中的代码是相同的[如果不是,你就违反了"一个定义规则" - 也就是说,一个函数应该有一个定义,无论它实际定义多少次]。

因此,在完成可执行文件时,编译器/链接器应该完全允许将两个相同的函数合并为一个。

但请注意,就目前而言,Foo::foo()被声明为内联函数,这意味着它不会"导出"到外部世界,并且应该没有冲突。

如果要在 foo1.cpp 和 foo2.cpp 中"手动包含"Foo 的类定义,并在函数中进行一些细微的区分,您会发现链接器"选择"其中一个函数,并丢弃另一个函数。它选择的哪个没有定义,而且由于"一个定义规则"已被打破,你"超出了你应该做的事情的范围",所以抱怨编译器"没有做正确的事情"是没有意义的。[尽管您必须使函数"非内联"才能使此问题,然后您可能会遇到多个定义的链接器错误]。

函数

Foo1Foo2都在各自的平移单元中。 当您包含 foo.h 时,两者都会获得整个Foo类的副本。 因此,链接器使用每个单元各自的副本。

现在,如果您将Foo::foo()嵌入到它自己的源文件中,那么Foo1Foo2将在链接期间使用相同的foo函数。