与C中的静态库链接

时间:2018-11-05 06:18:45

标签: c linker static-linking

嗨,我是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中未定义的符号?

1 个答案:

答案 0 :(得分:1)

如果您的C教科书不清楚,请联系 作者试图以此说明的行为 锻炼不是C标准规定的,实际上是行为 GNU binutils链接器ld-Linux中的默认系统链接器, 通常由gcc|g++|gfortran等代表您调用-可能 但不是不必要您可能会遇到的其他链接器的行为。

如果您正确地给了我们练习,那么作者可能是不懂静态的人 链接得很好,这对于编写有关它的教科书是最好的,或者也许不是 表达自己的态度。

除非我们要链接程序,否则默认情况下链接器不会 甚至坚持解决所有符号引用。所以大概我们是 链接程序(不是共享库),如果答案为

 gcc p.o libx.a liby.a libx.a

实际上是教科书上所说的,然后就是一个程序。

但是程序必须具有main函数。 main函数在哪里 它与p.olibx.aliby.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.aliby.a

$ ar rcs libx.a x.o cally.o callp.o
$ ar rcs liby.a y.o callx.o

现在,p.olibx.aliby.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,并且pp.o中定义了

  • 使用p.olibxz.aliby.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已解决。


[1]如-trace输出所示,严格来说,链接不是 完成,直到链接(libx.a)x.o之后的所有样板, 但是每个C程序链接都是相同的样板。