前言
最近遇到了一个非常奇怪的问题,编译时竟因为链接库的顺序不同,就有完全不同的结果。代码非常简单如下所示:
#include "muduo/net/EventLoop.h"
int main() {
muduo::net::EventLoop loop1;
muduo::net::EventLoop loop2;
return 0;
}
只是在一个线程内创建两个 EventLoop 对象,这是不被允许的,应该会报错。我使用如下命令编译:
g++ test.cc -I ../include -L ../lib -lpthread -lmuduo_base -lmuduo_net
此时却报了一堆未定义错误:
EventLoop.cc:205: undefined reference to `muduo::Logger::Logger(muduo::Logger::SourceFile, int, muduo::Logger::LogLevel)'
EventLoop.cc:205: undefined reference to `muduo::LogStream::operator<<(long)'
EventLoop.cc:205: undefined reference to `muduo::Logger::~Logger()'
这些东西都是在 muduo_base 中定义的,我明明链接的 muduo_base 库,为什么会有这些报错呢?当我改变链接库的顺序时,神奇的一幕出现了。
qgw@virtual-machine:~/build/release-install-cpp11/examples$ make
g++ echo.cc -I ../include -L ../lib -lpthread -lmuduo_net -lmuduo_base
qgw@virtual-machine:~/build/release-install-cpp11/examples$ ./a.out
20230529 06:44:22.218800Z 5020 DEBUG EventLoop EventLoop created 0X7FFFD30BDFB0 in thread 5020 - EventLoop.cc:65
20230529 06:44:22.218835Z 5020 DEBUG EventLoop EventLoop created 0X7FFFD30BE050 in thread 5020 - EventLoop.cc:65
20230529 06:44:22.218837Z 5020 FATAL Another EventLoop 0X7FFFD30BDFB0 exists in this thread 5020 - EventLoop.cc:68
已放弃 (核心已转储)
编译成功了,运行程序也得到了我想要的结果。这究竟是为什么呢?下面就来为你解答这一迷题。
理论基础
编译过程
先来简单了解下,从我们编写的程序到形成可执行程序的过程。大致可以分为 4 个步骤,分别是预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。
经过编译、汇编形成的目标文件并不能直接执行,它首先需要载入到链接器中。链接器确认 main 函数为初始进入点,把符号引用绑定到内存地址,把所有的目标文件集中在一起,再加上库文件,从而产生可执行文件。
静态链接
动态链接是将函数库加载到进程地址空间,程序在运行时寻找它们,而不是把函数库二进制代码作为自身可执行程序的一部分。
如果函数库的一份副本是可执行文件的物理组成部分,那么我们称之为静态链接。简单点说,就是静态链接需要将我们用到的函数代码,拷贝到可执行程序中,之后就不再需要库了。
这两者在提取符号上有很大的不同:
在动态链接中,所有的库符号加载到输出文件的虚拟地址空间中,所有的符号对于链接在一起的所有文件都是可见的。
在静态链接中,在处理静态库时,它只是在静态库中查找载入器当前所知道的未定义符号。换句话说,在编译器命令行中各个静态链接库的顺序是非常重要的。
链接器会被「函数库是在哪里提到的?」「它是以什么次序出现的?」之类的问题搞的晕头转向,因为符号是通过从左到右的顺序进行解析的。在尚未出现未定义的符号时,它不会从函数库中提取任何符号。为了能从静态库中提取所需的符号,首先需要让文件包含未解析的引用。
解答
有上面的知识,开头的问题也就很好解释了。muduo 使用的是静态库,在最开始的时候,我先链接了 muduo_base 库,又链接了 muduo_net 库。
程序中只有 EventLoop 一个未定义的符号,扫描到 muduo_base 库,此时并没有能从该库提取的符号,继续扫描到 muduo_net 库,找到了 EventLoop 符号。由于 EventLoop 中用到 Logger 打印日志、Timestamp 记录时间等等。这些符号都是在 muduo_base 中定义的,但 muduo_base 已经扫描过了,向后寻找也没找到这些符号的定义,于是出现了未定义错误。
当我改变链接顺序时,先扫描 muduo_net 找到了大量定义于 muduo_base 的符号,再扫描 muduo_base 找到这些符号的定义,也就编译成功了。