1. 什么是分离式编译?
一个项目由若干个源文件共同实现,而每个源文件(.cpp)单独编译成目标文件(.obj),最后将所有目标文件连接起来形成单一的可执行文件(.exe)的过程。
---------------test.h-------------------
void f();//这里声明一个函数f
---------------test.cpp--------------
#include”test.h”
void f()
{
…//do something
} //这里实现出test.h中声明的f函数
---------------main.cpp--------------
#include”test.h”
int main()
{
f(); //调用f,f具有外部连接类型
}
上面程序在编译器内部的过程为:
- 在编译mian.cpp的时候,编译器并不知道f的实现,所以当碰到对f的调用时只是给出一个指示,指示连接器为它寻找f的实现体,所以main.obj中没有关于f实现的二进制代码。
- 在编译test.cpp的时候,编译器找到了f的实现,所以在test.obj里有f实现的二进制代码。
- 连接时,连接器在test.obj中找到f实现的二进制地址,然后将main.obj中未解决的f地址替换成该二进制地址。
2. 为什么编译器不能支持对模板的分离式编译?
-------------test.h----------------
template<class T>
class A
{
public:
void f(); // 这里只是个声明
};
---------------test.cpp-------------
#include”test.h”
template<class T>
void A<T>::f() // 模板的实现
{
…//do something
}
---------------main.cpp---------------
#include”test.h”
int main()
{
A<int> a;
f(); // #1
}
上面程序在编译器内部的过程为:
- 与前一个相似
- 此时test.cpp中,只有f的实现,却没有f的调用。模板函数如果没有经过实例化,无法转换成二进制代码。所以f得不到实例化,那么test.cpp中也不会有f的二进制代码。
- 此时编译器也找不到f的实现,所以会抛出一个连接错误的异常
以下内容来自:引用1
3. 静态链接库与动态链接库
3.1 原始的链接方法
/********
* test.c
*********/
#include<stdio.h>
int main()
{
int num;
scanf("%d",&num);
printf("ok %d\n",num);
return 0;
}
假设printf的代码存在 printf.c,scanf的代码存在scanf.c,翻译之后变为可重定位模块.o

这种链接形式所存在的问题:
- 每次链接都需要显式的列出所有.o文件
- 不同程序如果要使用相同的模块,就要将模块链接到相应的程序中,影响效率
为了解决第一个问题,出现了静态链接库
3.2 静态链接库
静态库的核心思想是,将不同的可重定位的模块.o打包成一个文件,在链接的时候会自动从文件中抽取所需要用到的模块,这样就无需手动列出要用的模块
那么这就会出现一个问题:生成的文件比源文件大得多,存在相当大的空间浪费。怎么办呢?这时候就提出了动态链接
3.3 动态链接库
动态链接库的核心思想:代码共享和延迟绑定。代码共享依靠虚拟存储器实现,延迟绑定的核心在于两张表:PLT(Procedure Linkage Table)和GOT(Global Offset Table)。基于虚拟存储器的代码共享使得在内存中只存在某个模块的唯一一份代码,通过虚拟存储器的内存映射机制将物理地址空间中的代码映射到不同进程的虚拟地址空间中(如下图)。

编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享内存中找到可执行代码,最终达到运行时连接的目的
它的缺点:运行时加载,可能会影响前期性能。
网友评论