gpt4 book ai didi

c++ - 与静态库链接时,为什么要强制执行命令(例如source.cxx -lstatic)?

转载 作者:行者123 更新时间:2023-11-28 01:37:08 25 4
gpt4 key购买 nike

与静态库链接时,为什么执行命令?


  g ++ -ldynamic -lstatic src.cxx //错误
  
  g ++ -lstatic src.cxx -ldynamic //错误
  
  g ++ src.cxx -ldynamic -lstatic //成功
  
  g ++ -ldynamic src.cxx -lstatic //成功


是否有技术上的原因导致静态库不能像动态库一样(以任何顺序)链接?

为什么不能使链接库变得通用(可能是在编译/链接时提到的,例如对于static:-ls和dynamic:-ld等)?

最佳答案

Linux链接中的按需拆分

你的例子:

g++ -ldynamic -lstatic src.cxx # ERROR

g++ -ldynamic src.cxx -lstatic # SUCCESS


表示您的Linux发行版属于RedHat家族。让我们确认一下
在CentOS 7上说:

$ cat /proc/version
Linux version 3.10.0-693.el7.x86_64 (builder@kbuilder.dev.centos.org) \
(gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC) ) \
#1 SMP Tue Aug 22 21:09:27 UTC 2017


$ cat foo.c
#include <stdio.h>

void foo(void)
{
puts(__func__);
}

$ cat bar.c
#include <stdio.h>

void bar(void)
{
puts(__func__);
}

$ cat main.c
extern void foo(void);
extern void bar(void);

int main(void)
{
foo();
bar();
return 0;
}

$ gcc -Wall -fPIC -c foo.c
$ gcc -shared -o libfoo.so foo.o
$ gcc -Wall -c bar.c
$ ar cr libbar.a bar.o
$ gcc -Wall -c main.c
$ gcc -o prog -L. -lfoo -lbar main.o -Wl,-rpath=$(pwd)
main.o: In function `main':
main.c:(.text+0xa): undefined reference to `bar'
collect2: error: ld returned 1 exit status
# :(
$ gcc -o prog -L. -lfoo main.o -lbar -Wl,-rpath=$(pwd)
$ # :)
$ ./prog
foo
bar


所以你就在那里。

现在让我们在Debian氏族的发行版中进行检查:

$ cat /proc/version
Linux version 4.13.0-32-generic (buildd@lgw01-amd64-016) \
(gcc version 7.2.0 (Ubuntu 7.2.0-8ubuntu3)) \
#35-Ubuntu SMP Thu Jan 25 09:13:46 UTC 2018


在这里,一切都一样:

$ gcc -o prog -L. -lfoo -lbar main.o -Wl,-rpath=$(pwd)
main.o: In function `main':
main.c:(.text+0x5): undefined reference to `foo'
main.c:(.text+0xa): undefined reference to `bar'
collect2: error: ld returned 1 exit status


当它变得不同时。现在,链接无法解析 foo-从
共享库 libfoo.so-或 bar-来自静态库 libbar.a。和
解决我们需要的:

$ gcc -o prog -L. main.o -lfoo -lbar -Wl,-rpath=$(pwd)
$ ./prog
foo
bar


在目标文件后面提到的所有库- main.o-
引用它们定义的符号。

Centos-7(RedHat)的链接行为是过时的。 Ubuntu 17.10(Debian)
链接行为是在2013年的Debian 7中引入的,并被详细介绍
来自Debian的发行版。如您所见,它消除了区别
共享库和静态库之间关于库的需要,
在所有输入之后,是否需要出现在链接序列中
引用它的文件。它们都必须以依赖性顺序(DO1)出现,
共享库和静态库都一样。

这取决于发行版决定如何构建其GCC版本
工具链-他们如何选择传递给系统的默认选项
一种语言调用链接器( ld)在幕后
前端( gccg++gfortran等)为您执行链接。

具体来说,取决于链接器选项 --as-needed
在库之前,默认情况下是否插入或不插入 ld命令行
被插入。

如果 --as-needed无效,则到达共享库 libfoo.so
那么它将被链接,无论该链接到目前为止是否已产生
对共享库定义的符号的未解析引用。简而言之,
它将被链接,无论是否需要链接。也许更进一步
链接的进展,以及随后的投入,将产生未解决的参考
libfoo.so解决,证明其链接合理。但也许不是。它被链接
无论如何。那就是RedHat的方式。

如果到达 --as-neededlibfoo.so生效,则它
当且仅当它导出至少一个符号的定义时,才会被链接
链接中已经产生了尚未解决的参考,即
已经证明需要链接它。如果有的话,它最终不能链接
无需链接。这就是Debian的方式。

带有共享库链接的RedHat方式一直流行到Debian 7
打破了行列。但是静态库的链接始终符合所需原理
默认。没有适用于静态库的 --as-needed选项。
相反,存在相反的 --whole-archive
您需要使用它来覆盖默认行为,并从静态库链接对象文件,而不管是否需要。
因此,像您这样的人,在RedHat土地上,会观察到这种令人困惑的差异:默认情况下,静态库
必须在DO中链接;对于共享库,默认情况下将执行任何顺序。
民间是Debian的土地,所以看到了这样的差异。

会吗

由于Redhat方式存在这种令人困惑的差异-因此
初学者的联动努力-很自然地要问为什么历史上
它是静态库所需的,但共享库则不需要,
当然,以及为什么它仍在RedHat土地上使用。

大大简化,链接器通过以下方式组装程序(或共享库)
逐步填充节和动态依赖项记录(DDRs2)
段和DDR的结构从空开始
最终成为OS加载程序可以解析并成功映射的二进制文件
进入进程地址空间:例如ELF可执行文件或DSO。 (部分
这是一个真正的技术术语。动态依赖记录不是。
为了方便起见,我现在刚刚提出。)

宽松地说,驱动此过程的链接器输入是目标文件,
共享库或静态库。但严格来说,他们要么
目标文件或共享库。因为静态库很简单
ar archive恰好是
目标文件。就链接器而言,它只是一个对象序列
它可能或不需要使用的文件,与符号表一起存档
链接器可通过它查询哪个目标文件(如果有)定义了符号。

当链接程序到达目标文件时,该目标文件将始终被链接
进入程序。链接器从不询问是否需要目标文件
(这可能意味着什么)。任何目标文件都是无条件的链接源
需求,进一步的投入必须满足。

输入目标文件时,链接程序必须将其分解为
它组成的输入部分并将其合并到输出中
程序中的各个部分。当输入部分S出现在一个对象中时
文件中,部分S可能会出现在其他目标文件中;
也许所有的人。链接器必须将所有输入的 S部分缝合在一起
放入程序中的单个输出 S部分,因此最终没有完成
组成输出节 S直到链接完成。

将共享库 libfoo.so输入到链接时,链接器输出
将DDR插入程序(如果它确定需要或不关心该库)。这是必不可少的备忘录,它将在运行时读取
加载程序,告诉它 libfoo.so是该进程的依赖项,即
🚧正在施工🚧;因此它将通过其标准搜索算法定位 libfoo.so
加载并将其映射到流程中。

消耗目标文件是相对昂贵的链接。消耗
共享库相对便宜-尤其是在链接器没有
必须事先弄清楚是否需要共享库。
对象文件的输入/输出部分处理通常比写出DDR更麻烦。
但比努力更重要的是,链接目标文件通常会使程序显着
更大,并且可以使其任意更大。链接共享库会添加
只有DDR,这总是一件小事。

因此,有一个值得推崇的理由,就是采用一种联动策略来拒绝
一个目标文件,除非需要它,但是允许共享库的链接
不需要。链接不必要的目标文件会增加任意数量的失效
权重给计划,对链接的负担却成比例。但
如果链接程序不必证明需要共享库,则它
可以轻松地将其链接到程序的大部分中。如果开发人员选择将共享库添加到链接中,则很有可能需要它。红帽
坎普仍然认为理由足够充分。

当然,Debian阵营也有一个值得尊重的理由。是的,有Debian关联
需要花费额外的精力来确定 libfoo.so
到达,定义该处有未解析引用的任何符号
点。但是,仅通过链接所需的共享库即可:-


在运行时,加载程序避免了冗余加载的浪费
依赖性,或者弄清楚它们是多余的,以免加载它们。
如果运行时相关性,则可以简化软件包管理
冗余的运行时依赖项在链接时被清除。
像您一样,开发人员也不会被不一致的链接规则所绊倒
用于静态和共享库! -由于以下事实而加剧的障碍
链接器命令行中的 -lfoo不会显示它是否会解析
libfoo.solibfoo.a


分裂的每一面都有棘手的利弊。

现在考虑链接器如何使用静态库 libabc.a-目标文件 a.o, b.o, c.o的列表。
应用所需的原理,如下所示:链接器到达 libabc.a时,
它已携带0或多个未解析的符号引用
从0个其他目标文件和它已链接的共享库中转发
进入程序。链接器的问题是:是否有任何目标文件
提供这些未解析符号引用的任何定义的存档?
如果有0个此类未解析的引用,则答案是简单的否。
无需查看存档。 libabc.a被跳过。链接器移动
转到下一个输入。如果手头有一些未解决的符号引用,则
链接器检查由归档文件中的目标文件定义的符号。它
仅提取那些提供所需符号定义的对象文件(如果有)
3,然后将这些目标文件输入到链接中,就像它们是单独存在一样
在命令行中命名,根本没有提及 libabc.a。然后它移动
继续输入下一个输入(如果有)。

显而易见,静态库的按需原则暗示了DO。没有
目标文件将从静态库中提取并链接,除非未解决
对目标文件定义的某些符号的引用是从某些符号中产生的
目标文件(或共享库)已链接。

是否必须根据需要使用静态库?

在RedHat土地上,共享图书馆免于DO,我们在
它的缺失仅仅是链接提到的每个共享库。和我们一样
可以看出,这在链接资源和程序大小方面是可以接受的。要是我们
也取消了静态库的DO,等效策略将
是链接提到的每个静态库中的每个目标文件。但
在链接资源和程序自重方面,这是非常昂贵的。

如果我们想摆脱静态库的DO,但仍然不链接
不需要目标文件,链接器如何进行?

也许是这样吗?


将所有明确提到的目标文件链接到程序中。
链接提到的所有共享库。
查看是否还有任何未解决的引用。如果是这样,那么-
从提到的所有静态库中提取所有目标文件
到可选目标文件池中。
然后根据需要对这个可选池进行链接
目标文件,是成功还是失败。


但是这样的事情不会飞。链接器符号的第一个定义
看到的是链接的那个。这意味着对象文件的顺序
是链接的事项,即使是在两个不同的链接顺序之间
成功。

假设目标文件 a.o, b.o已被链接;未解决的参考
保留,然后链接器可以选择可选的目标文件 c.o, d.o, e.o, f.o
继续。

c.o, d.o, e.o, f.o的顺序可能不止一个
解决所有参考并给我们一个程序。链接可能是这种情况,
例如, e.o首先解析所有未完成的引用,而不会产生新的引用,
提供程序;链接时说 c.o首先还可以解决所有未解决的问题
引用,但产生了一些新引用,需要链接一些或
d.o, e.o, f.o的全部-取决于顺序-每个可能的链接
导致了另一个不同的程序。

那不是全部。 c.o, d.o, e.o, f.o可能有一个以上的排序,这样,在链接了某个目标文件后-点P-全部
以前出色的参考文献已解决,但其中:


其中一些排序在P点没有产生新的引用,或者仅产生一些进一步的链接顺序可以解析的引用。
其他的在点P产生新的参考,无法进一步解析链接顺序。


因此,每当链接器发现它在较早的时候做出了2类选择时,就需要
回溯到这一点并尝试其他尚未尝试的选择,
并且仅得出以下结论:如果未成功尝试所有链接,链接就会失败。
像这样对N个可选目标文件池进行链接需要花费时间
使阶乘N失败。

像现在一样使用DO作为静态库,我们在目标文件中指定目标文件和/或静态库Ij。
链接器命令行的顺序:

I0, I1, ... In


这等同于目标文件的排序,出于参数考虑,这可能会
裙装:

O0, O1, [02,... O2+j], O2+j+1, [O2+j+2,... O2+j+k] ...


其中 [Oi...]是可选目标文件(即静态库)的子序列,
在那时对链接器可用。

无论我们在编写命令行时是否不知道,我们都在断言不仅该命令是
可以链接以生成某些程序的良好DO排序,但同时
订购将产生我们想要的程序。

我们可能在第一次计算时就弄错了(=链接失败)。我们甚至可能是
在第二个错误(=平均链接错误)。但是如果我们不再关心这些顺序
输入,然后以某种方式将其留给链接器,以在它们上找到一个好的DO,或者证明没有输入,
然后:


实际上,我们已经不再关心将要获得的程序(如果有)。
我们已经不再关心链接是否会在任何可行的时间终止。


这不会发生。

我们不能针对DO损坏发出警告吗?

在评论中,您询问为什么链接器至少不能警告我们,如果我们的
静态对象文件和静态库不在DO中。

就像现在那样,这将使链接失败。但是给我们这个
附加警告,链接器将必须证明链接失败
因为目标文件和静态库不在DO中,而不仅仅是因为
链接中有没有引用定义的引用。而且
只能通过证明以下事实证明链接因DO中断而失败
某些程序可以通过对目标文件和静态库的某些排列来链接。
那是一项析因规模的任务,我们不在乎是否可以链接某些程序,
如果我们打算链接的程序不能是:链接器没有有关
除了输入的内容外,我们打算链接什么程序,顺序为
我们给他们。

如果提到了任何库,很容易使链接器(或更合理的说是GCC前端)发出警告
在命令行中任何对象文件之前。但这会带来一些麻烦的价值,因为
这样的联系不一定会失败,实际上可能是预期的
连锁。 “目标文件后的库”只是例程的良好指导
通过GCC前端调用链接器。更糟糕的是,这样的警告仅适用于库之后的目标文件,而不适用于库之间的DO损坏的情况,因此它只会做一些工作。



[1]我的缩写。

[2]也是我的缩写。

[3]更准确地说,从静态库提取目标文件是递归的。
链接器提取任何定义了未解析引用的目标文件。
已经掌握了,或者在
链接从库中提取的目标文件。

关于c++ - 与静态库链接时,为什么要强制执行命令(例如source.cxx -lstatic)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48813625/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com