我了解链接器/加载器如何使用静态/动态库。
但是,为什么不使用单一类型的库文件并带有指示该库应如何链接的编译器标志(静态与动态)呢?
通过简单的事实,我们确实拥有静态和动态库,我认为这些文件具有特定的内容,分别可以启用静态和动态链接。有人可以阐明静态库文件和共享库文件的内容之间的区别吗?
最佳答案
可惜静态库和动态库这两个词
这两种形式都是ADJECTIVE库,因为它永久地导致程序员
认为它们表示本质上相同的事物的变体。
这几乎与羽毛球法院和最高法院的想法一样具有误导性
本质上是同一种东西。实际上,它具有更大的误导性,
因为没有人真正想到羽毛球馆和最高法院
法院本质上是同一种东西。
有人可以阐明静态库文件和共享库文件的内容之间的区别吗?
让我们使用示例。推反羽毛球场/最高场雾
我将使用更准确的技术术语。我会说ar
存档而不是静态库,而我会说动态库
动态共享对象,简称DSO。
什么是ar
存档
我将从这三个文件开始创建一个ar
存档:
foo.c
#include <stdio.h>
void foo(void)
{
puts("foo");
}
bar.c
#include <stdio.h>
void bar(void)
{
puts("bar");
}
limerick.txt
There once was a young lady named bright
Whose speed was much faster than light
She set out one day
In a relative way
And returned on the previous night.
我将把这两个C源代码编译为“位置无关”目标文件:
$ gcc -c -Wall -fPIC foo.c
$ gcc -c -Wall -fPIC bar.c
无需将目标文件编译为
ar
存档-fPIC
。我只希望这些文件以这种方式编译。然后,我将创建一个名为
ar
的libsundry.a
存档,其中包含目标文件foo.o
和bar.o
,加
limerick.txt
:$ ar rcs libsundry.a foo.o bar.o limerick.txt
当然,会使用
ar
创建ar
存档,GNU通用存档器。因此,它不是由链接器创建的。没有联系
发生。以下是
ar
报告存档内容的方式:$ ar -t libsundry.a
foo.o
bar.o
limerick.txt
存档中的打油诗如下所示:
$ rm limerick.txt
$ ar x libsundry.a limerick.txt; cat limerick.txt
There once was a young lady named bright
Whose speed was much faster than light
She set out one day
In a relative way
And returned on the previous night.
问:将两个目标文件和一个ASCII原始文件放入同一个
ar
存档有什么意义?答:表明我可以。要显示
ar
存档只是一袋文件。让我们看看
file
由libsundry.a.
构成的$ file libsundry.a
libsundry.a: current ar archive
现在,我将编写一些在链接中使用
libsundry.a
的程序。fooprog
extern void foo(void);
int main(void)
{
foo();
return 0;
}
编译,链接和运行该代码:
$ gcc -c -Wall fooprog.c
$ gcc -o fooprog fooprog.o -L. -lsundry
$ ./fooprog
foo
那太笨拙了。显然,链接器不受存在的困扰
libsundry.a
中的ASCII打油诗。这样做的原因是链接器甚至没有尝试链接
limerick.txt
进入程序。让我们再次进行链接,这次使用诊断选项
这将向我们确切显示链接了哪些输入文件:
$ gcc -o fooprog fooprog.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
fooprog.o
(./libsundry.a)foo.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/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
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
那里有很多默认库和目标文件,但是唯一的对象
我们创建的链接器消耗的文件是:
fooprog.o
(./libsundry.a)foo.o
链接器对
./libsundry.a
所做的全部操作使foo.o
脱离了包并在程序中将其链接。将
fooprog.o
链接到程序后,它需要找到
foo
的定义。它看着袋子。它在
foo.o
中找到了定义,因此将foo.o
从袋子并将其链接到程序中。在链接
fooprog
时,gcc -o fooprog fooprog.o -L. -lsundry
与以下链接完全相同:
$ gcc -o fooprog fooprog.o foo.o
file
对fooprog
怎么说?$ file fooprog
fooprog: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), \
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, \
for GNU/Linux 2.6.32, BuildID[sha1]=32525dce7adf18604b2eb5af7065091c9111c16e,
not stripped
这是我的第二个程序:
foobarprog.c
extern void foo(void);
extern void bar(void);
int main(void)
{
foo();
bar();
return 0;
}
编译,链接和运行:
$ gcc -c -Wall foobarprog.c
$ gcc -o foobarprog foobarprog.o -L. -lsundry
$ ./foobarprog
foo
bar
这又是与
-trace
的链接:$ gcc -o foobarprog foobarprog.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
foobarprog.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/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
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
因此,这次,链接器消耗的目标文件为:
foobarprog.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
将
foobarprog.o
链接到程序后,需要找到foo
和bar
的定义。它看着袋子。它分别在
foo.o
和bar.o
中找到定义,因此将其从包并在程序中将它们链接起来。在链接
foobarprog
时,gcc -o foobarprog foobarprog.o -L. -lsundry
与以下链接完全相同:
$ gcc -o foobarprog foobarprog.o foo.o bar.o
总结所有这些。
ar
存档只是文件袋。您可以使用ar
归档文件,用于向链接器提供一堆目标文件,从中可以选择继续链接所需的内容。它将带那些目标文件
从包中取出并将它们链接到输出文件。它绝对没有其他
用于袋子。袋子对联动毫无贡献。
无需担心,手提包使您的生活更加简单
确切地说,特定链接需要什么对象文件。你只需要
要知道:好吧,他们在那个袋子里。
什么是DSO
让我们做一个。
foobar.c
extern void foo(void);
extern void bar(void);
void foobar(void)
{
foo();
bar();
}
我们将编译这个新的源文件:
$ gcc -c -Wall -fPIC foobar.c
然后使用
foobar.o
创建DSO,然后重新使用libsundry.a
$ gcc -shared -o libfoobar.so foobar.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbeginS.o
foobar.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
这使得DSO
libfoobar.so
。注意:DSO由链接器创建。它就像程序被链接一样被链接。
libfoopar.so
的链接看起来非常多类似于
foobarprog
的链接,但是该选项的添加-shared
指示链接器生成DSO而不是程序。在这里,我们看到了我们的对象链接消耗的文件为:
foobar.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
ar
根本不了解DSO:$ ar -t libfoobar.so
ar: libfoobar.so: File format not recognised
但是
file
可以:$ file libfoobar.so
libfoobar.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), \
dynamically linked, BuildID[sha1]=16747713db620e5ef14753334fea52e71fb3c5c8, \
not stripped
现在,如果我们使用
foobarprog
而不是libfoobar.so
重新链接libsundry.a
:$ gcc -o foobarprog foobarprog.o -L. -lfoobar -Wl,-trace,--rpath=$(pwd)
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
foobarprog.o
-lfoobar (./libfoobar.so)
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/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
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
我们看
foobarprog.o
-lfoobar (./libfoobar.so)
./libfoobar.so
本身已链接。在其中没有一些目标文件。那里里面没有任何目标文件。以及如何
可以在程序的动态依赖项中看到对链接有贡献的内容:
$ ldd foobarprog
linux-vdso.so.1 => (0x00007ffca47fb000)
libfoobar.so => /home/imk/develop/so/scrap/libfoobar.so (0x00007fb050eeb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb050afd000)
/lib64/ld-linux-x86-64.so.2 (0x000055d8119f0000)
该程序在运行时依赖于
libfoobar.so
。这就是链接DSO的作用。我们可以看到此运行时依赖关系得到满足。因此程序将运行:
$ ./foobarprog
foo
bar
和以前一样
DSO和程序(与
ar
存档不同)都是产品链接器的注释表明DSO和程序是基本上相同类型的事物的变体。
file
输出也表明了这一点。 DSO和程序都是ELF二进制文件OS加载程序可以映射到进程地址空间。不只是一袋文件。
ar
存档不是任何形式的ELF二进制文件。程序类型的ELF文件和非程序类型的ELF的区别在于不同的值
链接器写入ELF标头结构和程序标头中
ELF文件格式的结构。这些差异指示OS加载程序
加载程序类型的ELF文件时启动一个新进程,并进行扩充
加载非程序ELF文件时正在构建的过程。从而
非程序DSO被映射到其父程序的进程中。一个程序的事实
启动一个新过程要求程序应具有单个默认入口点
操作系统将控制权传递给的对象:该入口点是必需的
main
功能在C或C ++程序中。另一方面,非程序DSO不需要单个强制入口点。可以通过其导出的任何全局函数(通过来自
父程序。
但是从文件结构和内容的角度来看,DSO和程序
是非常相似的东西。它们是可以作为流程组成部分的文件。
程序必须是初始组件。 DSO可以是辅助组件。
进一步区分仍然很普遍:DSO必须完全由
可重定位代码(因为在链接时不知道加载器可能需要
将其放置在进程地址空间中),而程序则由绝对代码组成,
始终加载在同一地址。但实际上很有可能将可重定位链接
程序:
$ gcc -pie -o foobarprog foobarprog.o -L. -lfoobar -Wl,--rpath=$(pwd)
这就是
-pie
(位置独立的可执行文件)在这里执行的操作。然后:$ file foobarprog
foobarprog: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), ....
file
会说foobarprog
是DSO,尽管它是还是一个程序:
$ ./foobarprog
foo
bar
PIE可执行文件正在流行。在Debian 9和衍生发行版(Ubuntu 17.04 ...)中,GCC工具链
默认情况下会生成PIE程序。
如果您想了解
ar
和ELF
文件的详细知识格式,这是details of the
ar
format这是details of the ELF format。
为什么没有单一类型的库文件并带有编译器标志,
指出应该如何链接库(静态与动态)?
动态和静态链接之间的选择已经可以通过以下方式完全控制
命令行链接选项,因此无需放弃
ar
存档或DSO,也无需发明另一种图书馆实现这一目标。如果链接器无法使用
ar
存档,这将带来极大的不便。当然,如果链接器无法链接
DSO我们又回到了操作系统时代。
关于c - 静态库和动态库之间的差异,忽略了链接器/加载器的使用方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47116485/