链接器如何决定使用哪个实现
how linker decides which implementation use
假设我们有以下文件:
//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.h
和Foo::foo()
的定义时,它会认为它是一个名义上的内联函数,因为函数体出现在类中。 因此,编译器可能会也可能不会在调用点实际内联函数 - 该决定将取决于函数的大小/复杂性以及编译器的启发式和选项。 因此,对于两个翻译单元,Foo::foo
可能最终仍会成为.o
中单独的外联函数。
由于函数名义上是内联的,编译器需要确保该符号被标记为"弱符号"(确切的术语可能因操作系统/工具链而异 - 这是低于C++标准级别的实现细节( - 请参阅 http://en.wikipedia.org/wiki/Weak_symbol
当链接的对象具有相同的弱符号时,将保留一个副本中的代码,而丢弃其他副本中的代码。 因此,两个.o
文件都可以具有该功能(尽管由于类中的定义,它名义上是内联的(,但从.o
链接的可执行文件只有一个副本。
由于Foo::foo()
中的代码是相同的[如果不是,你就违反了"一个定义规则" - 也就是说,一个函数应该有一个定义,无论它实际定义多少次]。
因此,在完成可执行文件时,编译器/链接器应该完全允许将两个相同的函数合并为一个。
但请注意,就目前而言,Foo::foo()
被声明为内联函数,这意味着它不会"导出"到外部世界,并且应该没有冲突。
如果要在 foo1.cpp 和 foo2.cpp 中"手动包含"Foo
的类定义,并在函数中进行一些细微的区分,您会发现链接器"选择"其中一个函数,并丢弃另一个函数。它选择的哪个没有定义,而且由于"一个定义规则"已被打破,你"超出了你应该做的事情的范围",所以抱怨编译器"没有做正确的事情"是没有意义的。[尽管您必须使函数"非内联"才能使此问题,然后您可能会遇到多个定义的链接器错误]。
Foo1
和Foo2
都在各自的平移单元中。 当您包含 foo.h
时,两者都会获得整个Foo
类的副本。 因此,链接器使用每个单元各自的副本。
现在,如果您将Foo::foo()
嵌入到它自己的源文件中,那么Foo1
和Foo2
将在链接期间使用相同的foo
函数。
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 在决定是通过参考还是通过价值时,尺寸真的是一个问题吗
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 在c++中实现LinkedList时,应出现未处理的错误
- 为左值和右值的包装器实现C++范围
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- 使用GSoap实现ONVIF
- 决定放置函数实现的位置
- 如何实现两个类以自动决定深层和浅层复制
- 链接器如何决定使用哪个实现
- 头文件应该包括其他头文件,还是由实现来决定