C语言 共享对象(.so)、静态库(.a)和DLL(.so)之间的区别?

2izufjch  于 2023-10-16  发布在  其他
关注(0)|答案(5)|浏览(122)

我参与了一些关于Linux中库的争论,并想确认一些事情。
根据我的理解(如果我错了,请纠正我,我稍后会编辑我的帖子),在构建应用程序时有两种使用库的方法:
1.静态库(.a文件):在链接时,将整个库的副本放入最终应用程序中,以便调用应用程序始终可以使用库中的函数
1.共享对象(.so文件):在链接时,对象只是通过相应的头文件(.h)针对其API进行验证。这个库直到运行时才被实际使用,而运行时正是需要它的时候。
静态库的明显优势是它们允许整个应用程序自包含,而动态库的好处是“.so”文件可以被替换(即:在由于安全错误而需要更新的情况下),而不需要重新编译基本应用程序。
我听说有些人对共享对象和动态链接库(DLL)进行了区分,尽管它们都是“.so”文件。当涉及到在Linux或任何其他POSIX兼容操作系统(即:MINIX、UNIX、QNX等)?我被告知一个关键的区别(到目前为止)是共享对象只在运行时使用,而DLL必须首先使用应用程序中的dlopen()调用打开。
最后,我还听到一些开发人员提到“共享归档”,据我所知,它们本身也是静态库,但从未被应用程序直接使用。相反,其他静态库将链接到“共享存档”,以将一些(但不是全部)功能/资源从共享存档拉入正在构建的静态库中。

更新

**在这些术语提供给我的上下文中,它实际上是一个必须学习Linux的Windows开发人员团队使用的错误术语。我试图纠正他们,但(不正确的)语言规范仍然存在。

1.共享对象:程序启动时自动链接到程序中的一种库,并作为独立文件存在。该库在编译时包含在链接列表中(即:LDOPTS+=-lmylib(对于名为mylib.so的库文件)。库必须在编译时和应用程序启动时存在。
1.静态库:一种库,在构建时合并到实际程序本身中,用于单个(较大)应用程序,其中包含应用程序代码和库代码,在构建程序时自动链接到程序中,并且包含主程序和库本身的最终二进制文件作为单个独立二进制文件存在。该库在编译时包含在链接列表中(即:LDOPTS+=-lmylib(对于名为mylib.a的库文件)。库必须在编译时存在。

  1. DLL:本质上与共享对象相同,但不是在编译时包含在链接列表中,而是通过dlopen()/dlsym()命令加载库,以便在编译时程序不需要存在库。此外,库不需要(必须)在应用程序启动或编译时存在,因为它只在进行dlopen/dlsym调用时才需要。
    1.共享存档:本质上与静态库相同,但编译时带有“export-shared”和“-fPIC“标志。该库在编译时包含在链接列表中(即:LDOPTS+=-lmylibS(对于名为mylibS.a的库文件)。两者之间的区别在于,如果共享对象或DLL想要将共享存档静态链接到自己的代码中,并且能够使共享对象中的函数可用于其他程序,而不仅仅是在DLL内部使用它们,则需要使用此附加标志。当有人为您提供了一个静态库,而您希望将其重新打包为SO时,这很有用。库必须在编译时存在。

额外更新

DLL“和“shared library“之间的区别只是我当时工作的公司的一种(懒惰的,不准确的)口语(Windows开发人员被迫转向Linux开发,术语卡住了),坚持上面提到的描述。
此外,在“shared archives”的情况下,库名称后面的尾随“S“文字只是该公司使用的一种约定,而不是一般的行业。

yqkkidmi

yqkkidmi1#

**静态库(.a)**是可以直接链接到链接器生成的最终可执行文件的库;它包含在其中,并且不需要在将部署可执行文件的系统中具有库。
**共享库(.so)**是链接但未嵌入到最终可执行文件中的库,因此它将在可执行文件启动时加载,并且需要存在于部署可执行文件的系统中。

Windows上的动态链接库(.dll)类似于Linux上的共享库(.so),但这两种实现之间存在一些与操作系统相关的差异(Windows vs Linux):
一个
DLL
可以定义两种函数:出口和内部。导出的函数旨在由其他模块调用,以及从定义它们的DLL中调用。内部函数通常只能从定义它们的DLL中调用。
Linux上的SO库不需要特殊的导出语句来指示可导出的符号,因为所有符号都可用于查询进程。

sc4hvdpw

sc4hvdpw2#

我一直认为DLL和共享对象只是同一件事的不同术语- Windows称它们为DLL,而在UNIX系统上它们是共享对象,通用术语-动态链接库-涵盖了两者(甚至在UNIX上打开.so的函数也被称为dlopen())。
它们确实只在应用程序启动时链接,但是您对头文件进行验证的概念是不正确的。头文件定义了编译使用库的代码所需的原型,但在链接时,链接器会查看库本身,以确保它需要的函数确实存在。链接器必须在链接时找到函数体的某个位置,否则它将引发错误。它在运行时也会这样做,因为正如您正确指出的那样,库本身可能在程序编译后发生了变化。这就是ABI稳定性在平台库中如此重要的原因,因为ABI的变化会破坏针对旧版本编译的现有程序。
静态库只是编译器直接输出的对象文件包,就像你在项目编译中自己构建的对象文件包一样,所以它们以完全相同的方式被拉入并提供给链接器,并且以完全相同的方式删除未使用的位。

kzmpq1sx

kzmpq1sx3#

我可以详细说明Windows中DLL的细节,以帮助我在 * NIX世界的朋友们澄清这些谜团。
DLL类似于共享对象文件。两者都是图像,准备好由相应OS的程序加载器加载到存储器中。图像伴随着各种元数据,以帮助链接器和加载器进行必要的关联并使用代码库。
Windows DLL有一个导出表。可以按名称或表位置(数字)导出。后一种方法被认为是“老办法”,而且脆弱得多--重建DLL和更改函数在表中的位置将导致灾难,而如果入口点的链接是通过名称进行的,则没有真实的问题。所以,忘记这个问题,但是如果你使用“恐龙”代码,比如第三方供应商库,就要注意它的存在。
Windows DLL是通过编译和链接构建的,就像您对EXE(可执行应用程序)一样,但DLL不是独立的,就像SO是由应用程序使用的,通过动态加载或链接时绑定(对SO的引用嵌入在应用程序二进制的元数据中,OS程序加载器将自动加载引用的SO)。DLL可以引用其他DLL,就像SO可以引用其他SO一样。
在Windows中,DLL将只提供特定的入口点。这些被称为“出口”。开发者可以使用一个特殊的编译器关键字来使一个符号外部可见(对其他链接器和动态加载器),或者可以将导出列在一个模块定义文件中,该文件在创建DLL本身时在链接时使用。现代的做法是用关键字修饰函数定义以导出符号名。也可以创建带有关键字的头文件,这些关键字将该符号声明为要从当前编译单元之外的DLL导入的符号。查看关键字__declspec(dllexport)和__declspec(dllimport)以获取更多信息。
DLL的一个有趣特性是它们可以声明一个标准的“加载/卸载”处理函数。每当加载或卸载DLL时,DLL都可以根据具体情况执行某些初始化或清理操作。这很好地Map到将DLL作为面向对象的资源管理器,例如设备驱动程序或共享对象接口。
当开发人员希望使用已经构建的DLL时,她必须引用DLL开发人员在创建DLL时创建的“导出库”(*.LIB),或者她必须在运行时显式加载DLL并通过LoadLibrary()和GetProcAddress()机制按名称请求入口点地址。大多数情况下,使用DLL的方式是链接LIB文件(该文件仅包含DLL导出入口点的链接器元数据)。动态加载通常被保留用于在程序行为中实现“多态性”或“运行时可配置性”(访问附加组件或稍后定义的功能,也称为“插件”)。
Windows的做事方式有时会引起一些混乱;系统使用.LIB扩展名来引用正常的静态库(档案,如POSIX *.a文件)和在链接时将应用程序绑定到DLL所需的“导出存根”库。因此,应该始终查看 *.LIB文件是否具有同名的 *.DLL文件;如果不是,则 *.LIB文件很可能是静态库存档,而不是导出DLL绑定元数据。

tpxzln5u

tpxzln5u4#

你是正确的,静态文件在链接时复制到应用程序共享文件只是在链接时验证并在运行时加载
dlopen调用不仅用于共享对象,如果应用程序希望在运行时代表它这样做,否则共享对象将在应用程序启动时自动加载。DLLS和.so是一回事。dlopen的存在是为了为进程添加更细粒度的动态加载能力。您不必自己使用dlopen来打开/使用DLL,这也会在应用程序启动时发生。

f1tvaqid

f1tvaqid5#

我怀疑这里有某种误解,但是头文件,至少是用于编译源代码的.h类型的头文件,在链接时绝对不会被检查。
.h文件以及.c/.cpp文件只在编译阶段涉及,该阶段包括预处理。一旦创建了目标代码,在链接器开始处理之前,头文件就已经很好地完成了。

相关问题