我理解编译的基础知识.源文件编译为目标文件,然后链接器链接到可执行文件.这些目标文件由包含定义的源文件组成.
所以我的问题是:
Mic*_*her 57
从历史上看,目标文件完全或根本不链接到可执行文件中(现在,有一些例外,如功能级别链接或整个程序优化变得越来越流行),因此如果使用目标文件的一个函数,则可执行文件将全部接收他们
为了使可执行文件保持较小且没有死代码,标准库被拆分为许多小目标文件(通常大约为数百个).出于效率原因,拥有数百个小文件是非常不可取的:打开许多文件效率低下,并且每个文件都有一些松弛(文件末尾未使用的磁盘空间).这就是为什么目标文件被分组到库中,这有点像没有压缩的ZIP文件.在链接时,将读取整个库,并且当链接器开始读取它们所需的库或对象文件时,来自该库的所有目标文件(已解析已解析的符号)都包含在输出中.这可能意味着整个库必须立即在内存中以递归方式解决依赖关系.由于内存量非常有限,链接器一次只加载一个库,因此稍后在链接器命令行中提到的库不能使用前面在命令行中提到的库中的函数.
为了提高性能(加载整个库需要一些时间,特别是从软盘等慢速介质),库通常包含一个索引,告诉链接器哪些对象文件提供哪些符号.索引是由类似工具ranlib或库管理工具创建的(Borland tlib有一个生成索引的开关).只要有索引,即使所有目标文件都在磁盘缓存中并且从磁盘缓存加载文件是免费的,库也可以更有效地链接单个目标文件.
在保留头文件的同时,我可以替换.o或.a文件,并更改函数的功能(或它们如何执行),这是完全正确的.这是由使用的LPGL-license,它要求使用LGPL-licensed库的程序的作者为用户提供通过修补,改进或替代实现替换该库的可能性.运送自己应用程序的目标文件(可能被分组为库文件)足以为用户提供所需的自由; 无需发送源代码(如同GPL).
如果两组库(或目标文件)可以成功使用相同的头文件,则称它们与ABI兼容,其中ABI表示应用程序二进制接口.这比仅具有两组库(或目标文件)以及它们各自的头部更为狭窄,并且如果您使用此特定库的头文件,则保证可以使用每个库.这称为API兼容性,其中API表示应用程序接口.作为区别的示例,请查看以下三个头文件:
档案1:
typedef struct {
int a;
int __undocumented_member;
int b;
} magic_data;
magic_data* calculate(int);
Run Code Online (Sandbox Code Playgroud)
文件2:
struct __tag_magic_data {
int a;
int __padding;
int b;
};
typedef __tag_magic_data magic_data;
magic_data* calculate(const int);
Run Code Online (Sandbox Code Playgroud)
档案3:
typedef struct {
int a;
int b;
int c;
} magic_data;
magic_data* do_calculate(int, void*);
#define calculate(x) do_calculate(x, 0)
Run Code Online (Sandbox Code Playgroud)
前两个文件不相同,但是它们提供了可交换的定义(据我所料)不违反"一个定义规则",因此提供文件1作为头文件的库也可以用作文件2作为头文件.另一方面,File 3提供了一个与程序员非常相似的接口(在库作者承诺库的用户的所有内容中可能完全相同),但是使用File 3编译的代码无法链接到设计使用的库使用文件1或文件2,因为为文件3设计的库不会导出calculate,而只能导出do_calculate.此外,该结构具有不同的成员布局,因此使用文件1或文件2而不是文件3将无法正确访问b.提供文件1和文件2的库是ABI兼容的,但是所有三个库都是API兼容的(假设c和更强大的功能do_calculate不计入该API).
对于动态库(.dll,.so),情况完全不同:它们开始出现在可以同时加载多个(应用程序)程序的系统上(在DOS上不是这种情况,但在Windows上就是这种情况) .在内存中多次使用库函数的相同实现是浪费的,因此只将其加载到内存中具有不同的应用程序使用它可以节省内存.对于动态库,引用函数的代码不包含在可执行文件中,但仅包含对动态库内部函数的引用(对于Windows NE/PE,指定哪个DLL必须提供哪个函数;对于Unix .so文件,只指定了函数名和一组库).操作系统包含一个加载程序,即动态链接器,它解析这些引用并加载动态库(如果它们在程序启动时尚未在内存中).
Ser*_*sta 27
好的,让我们从头开始吧.
程序员(你)创建一些源文件,.cpp和.h.这两个文件之间的区别只是一个约定:
.cpp 意在编译.h 意在包含在其他源文件中但没有任何东西(除了担心有一个无法解决的事情)禁止你将cpp文件导入其他.cpp文件.
在C的早期(C++的祖先).h文件只包含函数,结构(没有C语言中的方法)和常量的声明.你也可以有一个宏(#define)但除此之外,不应该有代码.h.
在带有模板的C++中,您还必须添加.h模板类的实现,因为当C++使用模板而非Java等泛型时,模板的每个实例化都是不同的类.
现在回答你的问题:
每个.cpp文件都是一个编译单元.编译器将:
#include或#define(内部)生成完整的源代码.o或.obj)此对象格式包含:
然后(让我们暂时忘记库)链接器将所有编译单元放在一起并解析符号以创建可执行文件.
静态库更进一步.
静态库(通常.a或.lib)或多或少是一堆目标文件放在一起.存在是为了避免单独列出您需要的每个目标文件,即使用导出符号的目标文件.链接包含您使用的目标文件的库并链接目标文件本身是完全相同的.只需添加-lc,-lm或-lx11较短他们加入百.o文件.但至少在类Unix系统上,静态库是一个存档,你可以根据需要提取单个目标文件.
动态库完全不同.应将动态库视为特殊的可执行文件.它们通常使用相同的链接器构建,以创建正常的可执行文件(但具有不同的选项).但是,不是简单地声明一个入口点(在Windows上,.dll文件确实声明了一个可用于初始化的入口点.dll),它们声明了一个导出(和导入)符号的列表.在运行时,有系统调用允许获取这些符号的地址并几乎正常使用它们.但事实上,当您在动态加载库中调用例程时,代码驻留在加载器最初从您自己的可执行文件加载的内容之外.通常,从动态库加载所有使用过的符号的操作要么在加载时直接由加载器(在Unix类似的系统上)加载,要么在Windows上加载导入库.
现在回顾一下包含文件.好的旧K&R C和最新的C++都没有导入全局模块的概念,例如Java或C#.在这些语言中,当您导入模块时,您将获得其导出符号的声明,并指示您稍后将其链接.但是在C++中(与C相同)你必须单独完成:
.h源文件来完成,以便编译器知道它们是什么对象文件包含函数的定义,这些函数使用的静态变量以及编译器输出的其他信息.这是一种可以通过链接器连接的形式(例如,通过函数的入口点链接调用函数的点).
库文件通常打包为包含一个或多个目标文件(因此包含其中的所有信息).这提供了以下优点:分发单个库比分发对象文件更容易(例如,如果将编译对象分发给另一个开发人员以在其程序中使用),并且还使链接更简单(链接器需要被定向以访问更少的文件,这使得创建脚本更容易进行链接).此外,通常,链接器的性能优势很小 - 打开一个大型库文件并解释其内容比打开和解释许多小型目标文件的内容更有效,特别是如果链接器需要多次传递它们.还有一些小的优点,根据硬盘驱动器的格式化和管理方式,一些大文件比许多较小的文件消耗更少的磁盘空间.
将对象文件打包到库中通常是值得的,因为这是一次可以完成的操作,并且可以多次实现这些好处(每次链接器使用库来生成可执行文件时).
由于人类更好地理解源代码 - 因此更有可能使其正常工作 - 当它处于小块状态时,大多数大型项目都包含大量(相对)小的源文件,这些文件被编译为对象.将目标文件组装到库中 - 一步 - 提供了我上面提到的所有好处,同时允许人们以对人类而不是链接器有意义的方式管理其源代码.
也就是说,使用库是开发人员的选择.链接器并不关心,设置库并使用它比连接大量目标文件需要更多的努力.因此,没有什么能阻止开发人员使用目标文件和库的混合(除了显然需要避免多个对象或库中的函数和其他东西的重复,这导致链接过程失败).毕竟,开发人员的工作是制定管理软件构建和分发的策略.
实际上(至少)有两种类型的库.
链接器使用静态链接库来构建可执行文件,链接器将编译后的代码复制到可执行文件中.例如windows下的.lib文件和unix下的.a文件.库本身(通常)不需要与程序可执行文件分开分发,因为需要的部分是可执行文件.
动态链接库在运行时加载到程序中.两个优点是可执行文件较小(因为它不包含目标文件或静态库的内容),并且多个可执行文件可以使用每个动态链接库(即,只需要分发/安装一次库,并且所有使用这些库的可执行文件都可以工作).抵消这一点是程序的安装变得更加复杂(如果找不到动态链接库,则可执行文件将不会运行,因此安装过程必须至少应对安装库的潜在需求一次).另一个优点是可以更新动态库,而无需更改可执行文件 - 例如,修复库中包含的某个函数中的缺陷,从而修复使用该库而不更改可执行文件的所有程序的功能.如果仅在运行时找到旧版本的库,则依赖于库的最新版本的程序可能会出现故障.这给出了库的维护问题(通过各种名称调用,例如DLL地狱),特别是当程序依赖于多个动态链接库时.动态链接库的示例包括windows下的DLL,unix下的.so文件.操作系统提供的设施通常与操作系统一起以动态链接库的形式安装,允许所有程序(正确构建时)使用操作系统服务.
可以开发程序以使用静态和动态库的组合 - 再次由开发人员决定.静态库也可能链接到程序中,并处理与使用动态加载的库相关的所有簿记.