嗨,我是C和链接的初学者,我正在读一本书,该书对与静态库的链接有疑问:
a和b表示当前目录中的对象模块或静态库,并且 让a→b表示a取决于b,因为b定义了一个符号 被a引用。对于以下每种情况,显示最小命令 行(即目标文件和库参数最少的行)将 允许静态链接程序解析所有符号引用:
p.o → libx.a → liby.a and liby.a → libx.a →p.o
书中给出的答案是:
gcc p.o libx.a liby.a libx.a
我很困惑,答案不应该是:
gcc p.o libx.a liby.a libx.a p.o
否则libx.a
如何解决p.o
中未定义的符号?
答案 0 :(得分:1)
如果您的C教科书不清楚,请联系
作者试图以此说明的行为
锻炼不是C标准规定的,实际上是行为
GNU binutils
链接器ld
-Linux中的默认系统链接器,
通常由gcc|g++|gfortran
等代表您调用-可能
但不是不必要您可能会遇到的其他链接器的行为。
如果您正确地给了我们练习,那么作者可能是不懂静态的人 链接得很好,这对于编写有关它的教科书是最好的,或者也许不是 表达自己的态度。
除非我们要链接程序,否则默认情况下链接器不会 甚至坚持解决所有符号引用。所以大概我们是 链接程序(不是共享库),如果答案为
gcc p.o libx.a liby.a libx.a
实际上是教科书上所说的,然后就是一个程序。
但是程序必须具有main
函数。 main
函数在哪里
它与p.o
,libx.a
和liby.a
的链接关系是什么?这个
很重要,我们没有被告知。
因此,我们假设p
代表 program ,并且主函数位于
至少在p.o
中定义。尽管很奇怪liby.a
依赖
在p.o
上,其中p.o
是程序的主要对象模块,甚至
将main
函数定义在静态库的成员中很奇怪。
假设很多,下面是一些源文件:
p.c
#include <stdio.h>
extern void x(void);
void p(void)
{
puts(__func__);
}
int main(void)
{
x();
return 0;
}
x.c
#include <stdio.h>
void x(void)
{
puts(__func__);
}
y.c
#include <stdio.h>
void y(void)
{
puts(__func__);
}
callx.c
extern void x(void);
void callx(void)
{
x();
}
cally.c
extern void y(void);
void cally(void)
{
y();
}
callp.c
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
现在,p.o
,libx.a
和liby.a
满足了练习的条件:
p.o → libx.a → liby.a and liby.a → libx.a →p.o
因为:
p.o
引用但未定义x
,即
在libx.a
中定义。
libx.a
定义了cally
,它引用但未定义y
,
在liby.a
liby.a
定义了callx
,它引用但未定义x
,
在libx.a
中定义。
libx.a
定义了callp
,它引用但未定义p
,
在p.o
中定义。
我们可以通过nm
进行确认:
$ nm p.o
0000000000000000 r __func__.2252
U _GLOBAL_OFFSET_TABLE_
0000000000000013 T main
0000000000000000 T p
U puts
U x
p.o
定义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
p.o
首先被链接到程序中,因为输入.o
文件是
总是无条件链接。
然后是libx.a
。读
static-libaries
了解链接器如何处理它。链接p.o
后,它具有
仅一个未解决的引用-对x
的引用。它检查了libx.a
,以寻找
定义x
的目标文件。找到(libx.a)x.o
。它从x.o
中提取了libx.a
并将其链接,然后完成 。 1
涉及liby.a
的所有依赖关系:-
(libx.a)cally.o
取决于(liby.a)y.o
(liby.a)callx.o
取决于(libx.a)x.o
与链接无关,因为该链接不需要任何
liby.a
中的目标文件。
鉴于作者所说的是正确的答案,我们可以对产品进行反向工程 他们努力陈述自己的观点。就是这样:
定义p.o
的对象模块main
引用了一个符号x
未定义,并且x
在静态库x.o
的成员libxz.a
中定义
(libxz.a)x.o
指的是未定义的符号y
,并且y
是在静态库y.o
的成员liby.a
中定义的
(liby.a)y.o
是指未定义的符号z
,并且z
是在z.o
的成员libxz.a
中定义的。
(liby.a)y.o
指的是未定义的符号p
,并且p
在p.o
中定义了
使用p.o
,libxz.a
,liby.a
的最小链接命令是什么
会成功吗?
新的源文件:
p.c
Stays as before.
x.c
#include <stdio.h>
extern void y();
void cally(void)
{
y();
}
void x(void)
{
puts(__func__);
}
y.c
#include <stdio.h>
extern void z(void);
extern void p(void);
void callz(void)
{
z();
}
void callp(void)
{
p();
}
void y(void)
{
puts(__func__);
}
z.c
#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
现在正确了。
作者试图描述两个之间的相互依赖 静态库在程序的链接中,却摸索了这样一个相互依赖的事实 链接每个库中至少需要一个目标文件时,链接需要存在 指由 other 库中的目标文件定义的某些符号。
从更正后的练习中学到的教训是:
出现在链接器输入中的目标文件foo.o
永远不需要出现
不止一次,因为它将无条件链接,并且在链接时
链接它提供的任何符号s
的定义将用于解析
所有其他链接器输入中对s
的所有引用。如果foo.o
是
输入两次,只能得到s
的多重定义错误。
但是在链接中的静态库之间存在相互依赖性的地方 输入两个库之一可以 解决。因为一个目标文件 是从静态库中提取的,并且仅当该目标文件为 需要定义链接器试图定义的未解析符号引用 在输入库时 。因此,在更正的示例中:
p.o
已输入且无条件链接。x
成为未解决的引用。libxz.a
。x
中找到了(libxz.a)x.o
的定义。(libxz.a)x.o
被提取并链接。x
已解决。(libxz.a)x.o
是指y
。y
成为未解决的引用。liby.a
。y
中找到了(liby.a)y.o
的定义。(liby.a)y.o
被提取并链接。y
已解决。(liby.a)y.o
是指z
。z
成为未解决的引用。libxz.a
。。z
中找到libxz.a(z.o)
的定义libxz.a(z.o)
被提取并链接。z
已解决。
-trace
输出所示,严格来说,链接不是
完成,直到链接(libx.a)x.o
之后的所有样板,
但是每个C程序链接都是相同的样板。