与 C 中的静态库链接

阿姆贾德

嗨,我是 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

迈克·金汉

如果你的 C 教科书没有说清楚,作者试图用这个练习来说明的链接行为不是 C 标准规定的,实际上是 GNUbinutils链接器的行为ld- Linux 中的默认系统链接器,通常被调用代表您通过gcc|g++|gfortran等 - 可能但不一定是您可能遇到的其他链接器的行为。

如果你准确地给了我们练习,作者可能是一个不太了解静态链接的人,而不是编写关于它的教科书的最佳人,或者可能只是没有非常小心地表达自己。

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

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

实际上是教科书所说的,那么程序就是它必须的样子。

但是一个程序必须有一个main功能。main函数在哪里以及它与p.olibx.a和 的链接关系是liby.a什么?这很重要,我们没有被告知。

所以让我们假设p代表program,并且 main 函数至少定义在p.o. 奇怪的是,虽然liby.a取决于程序的主要对象模块p.o哪里p.o,但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.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阅读静态库以了解链接器如何处理它。链接后p.o,它只有一个未解析的引用 - 对x. 它检查libx.a寻找定义x. 它发现(libx.a)x.o. 它提取x.olibx.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它没有定义的符号,并xx.o静态库的成员定义libxz.a

  • (libxz.a)x.o引用y它没有定义的符号,并且yy.o静态库的成员定义liby.a

  • (liby.a)y.o指的z是它没有定义的符号,并z在 的成员z.o定义libxz.a

  • (liby.a)y.o指的p是它没有定义的符号,并pp.o

  • 什么是最小的联动指挥使用p.olibxz.aliby.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

  • 但是如果链接中的静态库之间存在相互依赖,则可以通过两次输入其中一个库来解决。因为目标文件是从静态库中提取并链接的,当且仅当需要该目标文件来定义链接器在输入库时试图定义的未解析符号引用所以在更正的例子中:

    • p.o 是输入和无条件链接。
    • x 成为未解决的参考。
    • libxz.a 是输入。
    • 的定义可在x中找到(libxz.a)x.o
    • (libxz.a)x.o 被提取和链接。
    • x 已解决。
    • 而是(libxz.a)x.oy
    • y 成为未解决的参考。
    • liby.a 是输入。
    • 的定义可在y中找到(liby.a)y.o
    • (liby.a)y.o 被提取和链接。
    • y 已解决。
    • 而是(liby.a)y.oz
    • z 成为未解决的参考。
    • libxz.a再次输入
    • 的定义zlibxz.a(z.o)
    • libxz.a(z.o) 被提取和链接。
    • z 已解决。


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

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

将C编译的静态库链接到C ++程序

来自分类Dev

Linux:C / C ++标准库静态与动态链接

来自分类Dev

如何更改静态链接库中的const字符串数组的Visual Studio C ++初始化序列

来自分类Dev

Windows中的C标准库链接

来自分类Dev

链接器:位于静态库中的const C结构的未定义引用

来自分类Dev

静态库链接,C ++,VS Express 2013

来自分类Dev

使用gcc在C中链接C ++静态库

来自分类Dev

链接器如何处理链接到共享库的C ++静态库的唯一typeinfo约束?

来自分类Dev

创建要与C ++程序链接的静态CUDA库

来自分类Dev

在C ++ Xcode中链接Armadillo库

来自分类Dev

C静态库无法与librt链接

来自分类Dev

在C ++中调用静态链接的静态方法

来自分类Dev

在Xcode iOS中链接C库

来自分类Dev

将C ++库与Haskell库静态链接

来自分类Dev

创建并链接静态锈库并链接到c

来自分类Dev

如果在多个共享库中调用并且启用了libstdc ++静态链接,则C ++流变得很糟糕

来自分类Dev

如何将C ++静态库链接到C程序?

来自分类Dev

是否可以将macfuse链接到C ++静态库?

来自分类Dev

Linux上的C ++,确认库中的代码未静态链接到生成的可执行文件中

来自分类Dev

在C ++中链接静态库时对...的未定义引用

来自分类Dev

与CMake链接的C ++动态和静态库

来自分类Dev

在C中链接Opencv库

来自分类Dev

静态库链接,C ++,VS Express 2013

来自分类Dev

使用gcc在C中链接C ++静态库

来自分类Dev

在C ++中链接库

来自分类Dev

如何为C扩展链接python的静态库?

来自分类Dev

C ++:内存中的静态库的多个副本

来自分类Dev

将静态库链接到 Visual C++ 中的新项目

来自分类Dev

C++ 在多个文件单元中链接同一个静态库会增加大小/膨胀吗?

Related 相关文章

热门标签

归档