编译器和解释器、静态库和动态库基本概念
解释器与编译器
- 解释器:直接执行用编程语言编写的指令的程序
- 编译器:把源代码转换成(翻译)低级语言的程序
实际上,解释器可以将源语言转换成某种中间形式(或语言)来加速执行,这就是这类语言通常依赖于虚拟机的原因,而这自然会导致一些问题:
- 所有使用虚拟机的语言都是解释型语言吗?
- 他们其实是编译运行的吗?
对于开发人员甚至是语言创作者来说,真正的重要性在于与他们的区别。 两者都有优点和缺点,实际上一些语言可以同时具有解释器和编译器,有的还不只一个。
主要观点仍然是:解释器立即执行代码,编译器为稍后的执行准备好(优化或其它处理后的适用于指定平台的)源代码。所有实际的差异都因为他们有不同的目标
常见编译语言:
- C
- C++
- Object-C
常见解释性语言:
- Java (Java是先编译后解释,可以是解释型语言也可以是编译型语言)
- JavaScript
- lua
- PHP
- Haskell
程序分发
一个重要的区别是编译器生成一个独立的程序,而解释的程序总是需要解释器来运行。
如果你有一个编译的程序,你不需要安装其他任何东西就可以运行起来,这十分简单。 另一方面,可执行程序一般只在特定平台上执行(例如Windows的exe程序就无法运行于Linux系统):不同的操作系统和不同的处理器需要不同的编译版本。
如果要解释程序,可以将不同平台上的相同副本分发给用户。 但是需要一个对应的解释器,以可以分发源代码或中间产物。
跨平台
使用解释型编程语言更容易制作跨平台程序
速度
就执行而言,确实是编译后执行的编译型程序执行的快些,但是编译型程序的编译加执行的时间比解释性语言解释执行的时间多。
编译器确实产生更快的程序,这是因为它必须把每个语句分析一次,而解释器必须每次都分析一次,此外,编译器还可以优化其生成的可执行代码。 这既是因为它确切地知道它将在哪里运行,并且需要时间来优化代码。
调试
在使用解释器比使用编译器时,调试更容易,有几个原因:
- 解释器只有一个可执行文件,不需要用于开发的调试版本,也不需要最终用户的发行版本;
- 使用解释器的平台特定错误较少;
- 由于解释器即时转换代码,源代码中的信息仍然可用;
- 由于解释器一次执行一个语句,因此更容易发现错误。
静态库和动态库
静态库和动态库是相对编译期和运行期,静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要该静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。
静态库 作用:
- 模块化,分工合作,提高了代码的复用及核心技术的保密程度
- 避免少量改动经常导致大量的重复编译连接
- 也可以重用,注意不是共享使用
动态库 好处:
- 可以将最终可执行文件体积缩小,将整个应用程序分模块,团队合作,进行分工,影响比较小
- 多个应用程序共享内存中得同一份库文件,节省资源
- 可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。
- 应用插件化
- 软件版本实时模块升级
大型软件工程的模块化或组件化,离不开静态库和动态库的运用
iOS 静态库和动态库的存在的形式
-
静态库:以.a 和 .framework为文件后缀名。
-
动态库:以.tbd(之前叫.dylib) 和 .framework 为文件后缀名。(系统直接提供给我们的framework都是动态库!)
理解:.a 是一个纯二进制文件,.framework 中除了有二进制文件之外还有资源文件。
.a
,要有.h
文件以及资源文件配合,.framework
文件可以直接使用。总的来说,.a + .h + sourceFile = .framework**。所以创建静态库最好还是用.framework
的形式
静态库和动态库区别
- 静态库在链接时,会被完整的复制到可执行文件中,如果app多个模块用到了静态库,那么每个模块都会拷贝一份
- 动态库不会复制,只有一份,
- 静态库和动态库都是闭源库,不会暴露内部具体的代码信息
静态库的处理方式
对于一个静态库而言,其实已经是编译好的了,类似一个 .o 的集合,这些.o文件是离散的没有经过链接。静态库链接的过程简单的讲就是合并,链接器只会将静态库中被使用的部分合并到可执行文件中去。具体步骤如下:
- 链接器会将所有.o用到的 global symbol 和 unresolved symbol 放入一个临时表,而且是 global symbol 是不能重复的。
- 对于静态库的 .o , 连接器会将没有任何 symbol 在 unresolved symbol table 的给忽略。
- unresolved symbol 类似
extern int test();
— .h的 声明? - global symbol 类似
void test() { print("test")}
– .m 的 实现? - 最后,链接器会用函数的实际地址来代替函数引用。
动态库的处理方式
对于动态库而言其实分 动态链接库 和 动态加载库 两种的,这两个最本质的区别还是加载时间。
- 动态链接库:在没有被加载到内存的前提下,当可执行文件被加载,动态库也随着被加载到内存中。在 Linked Framework and Libraries 设置的一些 share libraries。【随着程序启动而启动】
- 动态加载库:当需要的时候再使用 dlopen 等通过代码或者命令的方式来加载。【在程序启动之后】
动态链接是使用了 Procedure Linkage Table (PLT)。首先这个 PLT 列出了程序中每一个函数的调用,当程序开始运行,如果动态库被加载到内存中,PLT 会去寻找动态的地址并记录下来,如果每个函数都被调用过的话,下一次调用就可以通过 PLT 直接跳转了,但是和静态库还是有点区别的是,每一个函数的调用还是需要通过一张 PLT。这也正是 sunny 所说的所有静态链接做的事情都搬到运行时来做了,会导致更慢 的原因。
从源代码到运行程序
一个程序的编译执行过程包括如下步骤
- 预处理(Pre-process):把宏替换,删除注释,展开头文件,产生 .i 文件。
- 编译(Compliling):把之前的 .i 文件转换成汇编语言,产生 .s文件。
- 汇编(Asembly):把汇编语言文件转换为机器码文件,产生 .o 文件。
- 链接(Link):对.o文件中的对于其他的库的引用的地方进行引用,生成最后的可执行文件(同时也包括多个 .o 文件进行 link)。
其它
世界上第一个编译器,名字叫做 A-0,是天才格雷斯·霍珀(Grace Hopper)的点子。
原文
https://tomassetti.me/difference-between-compiler-interpreter/
既已览卷至此,何不品评一二: