您的链接在引用它们的目标文件之前使用库
- 您正在尝试使用 GCC 工具链编译和链接您的程序。
- 您的链接指定了所有必要的库和库搜索路径
- 如果
libfoo 依赖于libbar,那么您的链接正确地将libfoo 放在libbar 之前。
- 您的链接失败并出现
undefined reference to something 错误。
- 但所有未定义的 something 都在您拥有的头文件中声明
#included,实际上是在您要链接的库中定义的。
示例在 C 中。它们同样可以是 C++
一个涉及您自己构建的静态库的最小示例
my_lib.c
#include "my_lib.h"
#include <stdio.h>
void hw(void)
{
puts("Hello World");
}
my_lib.h
#ifndef MY_LIB_H
#define MT_LIB_H
extern void hw(void);
#endif
eg1.c
#include <my_lib.h>
int main()
{
hw();
return 0;
}
你构建你的静态库:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
你编译你的程序:
$ gcc -I. -c -o eg1.o eg1.c
您尝试将其与libmy_lib.a 链接并失败:
$ gcc -o eg1 -L. -lmy_lib eg1.o
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
如果你一步编译和链接,结果相同,例如:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
一个涉及共享系统库的最小示例,压缩库libz
eg2.c
#include <zlib.h>
#include <stdio.h>
int main()
{
printf("%s\n",zlibVersion());
return 0;
}
编译你的程序:
$ gcc -c -o eg2.o eg2.c
尝试将您的程序与libz 链接并失败:
$ gcc -o eg2 -lz eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
如果您一次性编译和链接,则相同:
$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
以及涉及pkg-config 的示例 2 的变体:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
你做错了什么?
在您要链接的目标文件和库的序列中,以使您的
程序,您将库放置在引用的目标文件之前
他们。您需要将库放置在引用的目标文件之后
给他们。
正确链接示例 1:
$ gcc -o eg1 eg1.o -L. -lmy_lib
成功:
$ ./eg1
Hello World
正确链接示例 2:
$ gcc -o eg2 eg2.o -lz
成功:
$ ./eg2
1.2.8
正确链接示例 2 pkg-config 变体:
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
解释
从这里开始阅读是可选的。
默认情况下,GCC 生成的链接命令在您的发行版中,
从左到右消耗链接中的文件
命令行序列。当它发现一个文件引用了 something
并且不包含它的定义,将搜索定义
在右侧的文件中。如果最终找到定义,则
参考解决。如果最后有任何引用仍未解决,
链接失败:链接器不向后搜索。
首先,示例1,带有静态库my_lib.a
静态库是目标文件的索引存档。当连接器
在链接序列中找到-lmy_lib 并确定这是指
到静态库./libmy_lib.a,它想知道你的程序是否
需要libmy_lib.a 中的任何目标文件。
libmy_lib.a中只有目标文件,即my_lib.o,并且只定义了一个东西
在my_lib.o中,即函数hw。
链接器将决定您的程序需要my_lib.o 当且仅当它已经知道
您的程序在一个或多个目标文件中引用了hw
添加到程序中,并且它没有添加任何目标文件
包含hw 的定义。
如果是这样,那么链接器将从库中提取my_lib.o 的副本并
将其添加到您的程序中。然后,您的程序包含hw 的定义,所以
它对hw 的引用已解决。
当你尝试像这样链接程序时:
$ gcc -o eg1 -L. -lmy_lib eg1.o
链接器没有添加 eg1.o 到程序中
-lmy_lib。因为那时,它还没有看到eg1.o。
你的程序还没有引用hw:它
根本没有做任何引用,因为它所做的所有引用
在eg1.o。
所以链接器没有将my_lib.o添加到程序中并且没有进一步的
用于libmy_lib.a。
接下来,它找到eg1.o,并将其添加到程序中。中的一个目标文件
链接序列总是添加到程序中。现在,该程序使
对hw 的引用,并且不包含hw 的定义;但
链接序列中没有任何东西可以提供缺失的
定义。对hw 的引用最终未解决,链接失败。
第二,示例2,共享库libz
共享库不是目标文件或类似文件的存档。它是
更像是一个没有main 函数的程序
而是公开它定义的多个其他符号,以便其他
程序可以在运行时使用它们。
如今,许多 Linux 发行版都配置了他们的 GCC 工具链,以便其语言驱动程序(gcc、g++、gfortran 等)
指示系统链接器 (ld) 在按需的基础上链接共享库。
您拥有其中一个发行版。
这意味着当链接器在链接序列中找到-lz,并确定这是指
到共享库(比如)/usr/lib/x86_64-linux-gnu/libz.so,它想知道它添加到您的程序中的任何尚未定义的引用是否具有由libz 导出的定义
如果是这样,那么链接器将不会从libz 中复制任何块并且
将它们添加到您的程序中;相反,它只会修改程序的代码
这样:-
您的程序只想引用具有由libz 导出的定义的事物,
即函数zlibVersion,在eg2.c 中仅被引用一次。
如果链接器将该引用添加到您的程序,然后找到定义
由libz 导出,引用已已解决
但是当你尝试像这样链接程序时:
gcc -o eg2 -lz eg2.o
事件的顺序是错误的,与示例 1 相同。
当链接器找到-lz 时,no 对任何内容的引用
节目中:都在eg2.o,目前还没有看到。所以
链接器决定它对libz 没有用处。当它到达eg2.o 时,将其添加到程序中,
然后对zlibVersion有未定义的引用,链接序列完成;
该引用未解析,并且链接失败。
最后,示例 2 的 pkg-config 变体现在有了明显的解释。
外壳扩展后:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
变成:
gcc -o eg2 -lz eg2.o
这只是示例 2。
我可以重现示例 1 中的问题,但不能重现示例 2 中的问题
联动:
gcc -o eg2 -lz eg2.o
非常适合您!
(或者:该链接在 Fedora 23 上对您来说很好,但在 Ubuntu 16.04 上却失败了)
那是因为链接工作的发行版是其中之一
没有将其 GCC 工具链配置为链接共享库按需。
在过去,类 unix 系统链接静态和共享是很正常的
不同规则的库。链接序列中的静态库已链接
在示例 1 中说明的按需基础上,但共享库是无条件链接的。
这种行为在链接时是经济的,因为链接器不必考虑
程序是否需要共享库:如果是共享库,
链接它。大多数链接中的大多数库都是共享库。但也有缺点:-
这种权衡导致了今天的分裂局面。一些发行版有
更改了共享库的 GCC 链接规则,以便 按需
原则适用于所有图书馆。一些发行版坚持使用旧的
方式。
为什么我同时编译链接还是会出现这个问题?
如果我这样做:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
gcc 肯定要先编译eg1.c,然后链接结果
带有libmy_lib.a 的目标文件。那么它怎么可能不知道那个目标文件
做链接的时候需要吗?
因为使用单个命令编译和链接不会改变
连接序列的顺序。
当你运行上面的命令时,gcc 发现你想要编译 +
连锁。所以在幕后,它会生成一个编译命令,然后运行
它,然后生成一个链接命令并运行它,就好像 你 已经运行了
两个命令:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
所以链接会失败,就像你做运行这两个命令一样。这
您在失败中注意到的唯一区别是 gcc 生成了一个
编译 + 链接情况下的临时目标文件,因为你没有告诉它
使用eg1.o。我们看到:
/tmp/ccQk1tvs.o: In function `main'
代替:
eg1.o: In function `main':
另见
The order in which interdependent linked libraries are specified is wrong
以错误的顺序放置相互依赖的库只是一种方法
您可以在其中获取需要定义即将发生的事物的文件
比提供定义的文件更晚。将库放在前面
引用它们的目标文件是犯同样错误的另一种方式。