[英]linking with static library in C
嗨,我是 C 和链接的初学者,我正在阅读一本关于与静态库链接有问题的书:
让 a 和 b 表示当前目录中的目标模块或静态库,让 a→b 表示 a 依赖于 b,因为 b 定义了一个被 a 引用的符号。 对于以下每种情况,显示允许静态链接器解析所有符号引用的最小命令行(即,具有最少数量的目标文件和库参数的命令行):
po → libx.a → liby.a and liby.a → libx.a →po
而书中给出的答案是:
gcc p.o libx.a liby.a libx.a
我很困惑,答案不应该是:
gcc p.o libx.a liby.a libx.a p.o
否则libx.a
的未定义符号如何由po
解析?
如果你的 C 教科书没有说清楚,作者试图用这个练习来说明的链接行为不是 C 标准规定的,实际上是 GNU binutils
链接器ld
行为 - Linux 中的默认系统链接器,通常由gcc|g++|gfortran
等代表您调用 - 可能但不一定是您可能遇到的其他链接器的行为。
如果你准确地给了我们练习,作者可能是一个不太了解静态链接的人,而不是编写关于它的教科书的最佳人,或者可能只是没有非常小心地表达自己。
除非我们正在链接一个程序,否则默认情况下链接器甚至不会坚持解析所有符号引用。 所以大概我们正在链接一个程序(不是共享库),如果答案是:
gcc p.o libx.a liby.a libx.a
实际上是教科书所说的,那么程序就是它必须的样子。
但是一个程序必须有一个main
函数。 main
函数在哪里,它与po
、 libx.a
和liby.a
链接关系是什么? 这很重要,我们没有被告知。
所以让我们假设p
代表program ,并且 main 函数至少在po
定义。 奇怪的是,虽然liby.a
依赖于po
,其中po
是程序的主要对象模块,但在静态库的成员中定义main
函数会更奇怪。
假设这么多,这里有一些源文件:
个人电脑
#include <stdio.h>
extern void x(void);
void p(void)
{
puts(__func__);
}
int main(void)
{
x();
return 0;
}
xc
#include <stdio.h>
void x(void)
{
puts(__func__);
}
yc
#include <stdio.h>
void y(void)
{
puts(__func__);
}
呼叫中心
extern void x(void);
void callx(void)
{
x();
}
呼叫.c
extern void y(void);
void cally(void)
{
y();
}
调用程序
extern void p(void);
void callp(void)
{
p();
}
将它们全部编译为目标文件:
$ gcc -Wall -Wextra -c p.c x.c y.c callx.c cally.c callp.c
并制作静态库libx.a
和liby.a
:
$ ar rcs libx.a x.o cally.o callp.o
$ ar rcs liby.a y.o callx.o
现在, po
、 libx.a
和liby.a
满足练习的条件:
p.o → libx.a → liby.a and liby.a → libx.a →p.o
因为:
po
指但不定义x
,它在libx.a
定义。
libx.a
定义了cally
,它引用但不定义y
,它在liby.a
定义
liby.a
定义了callx
,它引用但不定义x
,它在libx.a
定义。
libx.a
定义了callp
,它引用但不定义p
,它在po
定义。
我们可以用nm
确认:
$ nm p.o
0000000000000000 r __func__.2252
U _GLOBAL_OFFSET_TABLE_
0000000000000013 T main
0000000000000000 T p
U puts
U x
po
定义p
( = T p
) 并引用x
( = U x
)
$ nm libx.a
x.o:
0000000000000000 r __func__.2250
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T x
cally.o:
0000000000000000 T cally
U _GLOBAL_OFFSET_TABLE_
U y
callp.o:
0000000000000000 T callp
U _GLOBAL_OFFSET_TABLE_
U p
libx.a
定义x
( = T x
) 和引用y
( = U y
) 和引用p
( = U p
)
$ nm liby.a
y.o:
0000000000000000 r __func__.2250
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T y
callx.o:
0000000000000000 T callx
U _GLOBAL_OFFSET_TABLE_
U x
liby.a
定义y
( = T y
) 并引用x
( = U x
)
现在教科书的联动肯定成功了:
$ gcc p.o libx.a liby.a libx.a
$ ./a.out
x
但它是最短的链接吗? 不,这是:
$ gcc p.o libx.a
$ ./a.out
x
为什么? 让我们重新运行与诊断的链接,以显示实际链接了哪些目标文件:
$ gcc p.o libx.a -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
p.o
(libx.a)x.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
他们是:
p.o
(libx.a)x.o
po
首先链接到程序中,因为输入.o
文件总是无条件链接的。
然后是libx.a
。 阅读静态库以了解链接器如何处理它。 链接po
,它只有一个未解析的引用 - 对x
的引用。 它检查libx.a
寻找定义x
的目标文件。 它找到(libx.a)xo
。 它从libx.a
提取xo
并链接它,然后就完成了。 1
所有涉及liby.a
的依赖关系:-
(libx.a)cally.o
依赖于(liby.a)yo
(liby.a)callx.o
依赖于(libx.a)xo
是无关的联动,因为联动不需要任何目标文件liby.a
。
鉴于作者所说的是正确答案,我们可以对他们试图陈述的练习进行逆向工程。 就是这个:
定义main
的对象模块po
引用了它没有定义的符号x
,而x
在静态库libxz.a
成员xo
中定义
(libxz.a)xo
指的是它没有定义的符号y
,而y
是在静态库liby.a
成员yo
中定义的
(liby.a)yo
指的是它没有定义的符号z
,而z
在libxz.a
成员zo
中libxz.a
。
(liby.a)yo
指的是它没有定义的符号p
,而p
是在po
定义的
使用po
、 libxz.a
、 liby.a
的最小链接命令是什么?
新的源文件:
个人电脑
Stays as before.
xc
#include <stdio.h>
extern void y();
void cally(void)
{
y();
}
void x(void)
{
puts(__func__);
}
yc
#include <stdio.h>
extern void z(void);
extern void p(void);
void callz(void)
{
z();
}
void callp(void)
{
p();
}
void y(void)
{
puts(__func__);
}
零点
#include <stdio.h>
void z(void)
{
puts(__func__);
}
新的静态库:
$ ar rcs libxz.a x.o z.o
$ ar rcs liby.a y.o
现在链接:
$ gcc p.o libxz.a
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
失败,就像:
$ gcc p.o libxz.a liby.a
liby.a(y.o): In function `callz':
y.c:(.text+0x5): undefined reference to `z'
collect2: error: ld returned 1 exit status
和:
$ gcc p.o liby.a libxz.a
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
和(您自己的选择):
$ gcc p.o liby.a libxz.a p.o
p.o: In function `p':
p.c:(.text+0x0): multiple definition of `p'
p.o:p.c:(.text+0x0): first defined here
p.o: In function `main':
p.c:(.text+0x13): multiple definition of `main'
p.o:p.c:(.text+0x13): first defined here
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
因未定义引用错误和多重定义错误而失败。
但是教科书的答案是:
$ gcc p.o libxz.a liby.a libxz.a
$ ./a.out
x
现在是对的。
作者试图描述一个程序链接中两个静态库之间的相互依赖,但发现这种相互依赖只有在链接需要每个库中至少有一个引用某个符号的目标文件时才能存在。由另一个库中的目标文件定义。
从修正后的练习中要吸取的教训是:
出现在链接器输入中的目标文件foo.o
永远不需要出现多次,因为它将被无条件链接,并且当它被链接时,它提供的任何符号s
的定义将用于解析对s
所有引用任何其他链接器输入的累积。 如果foo.o
被输入两次,你只能得到s
多重定义的错误。
但是如果链接中的静态库之间存在相互依赖关系,则可以通过两次输入其中一个库来解决。 因为目标文件是从静态库中提取并链接的,当且仅当需要该目标文件来定义链接器在输入库时试图定义的未解析符号引用时。 所以在更正的例子中:
po
是输入并无条件链接。x
成为未解析的引用。libxz.a
是输入。x
的定义可以在(libxz.a)xo
。(libxz.a)xo
被提取和链接。x
已解决。(libxz.a)xo
指的是y
。y
成为未解析的引用。liby.a
是输入。y
的定义可以在(liby.a)yo
。(liby.a)yo
被提取和链接。y
已解决。(liby.a)yo
指的是z
。z
成为未解析的参考。libxz.a
。libxz.a(zo)
可以找到z
的定义libxz.a(zo)
被提取和链接。z
已解决。-trace
输出所示,严格来说,直到(libx.a)xo
之后的所有样板也被链接后,链接才完成,但对于每个 C 程序链接,它都是相同的样板。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.