编译和链接
写在前面
只讨论C语言。
“二进制代码”——指被能够计算机直接执行的代码(至于究竟是操作系统层的命令,还是CPU的机器指令,这里不做深入讨论)。
头文件
一般是.h文件。
头文件的功能在于指示编程人员和编译器:哪些函数可以调用?这些函数应该怎么调用(参数和返回值)?
头文件不指示任何库的存储位置。所有需要的库文件,应位于默认目录或指定目录。
中间代码文件
一般是.o或.obj文件。
中间代码文件包括:
- 一般语句的二进制编码。
- 用户定义函数的二进制编码和接口。
- 函数调用的指示(可以理解为“链接”或“指针”)。
中间代码文件的使命在链接完成后就结束了。
库
库是一个文件。
库的内容是形成库的若干个中间代码文件内容的集合。
库和中间代码文件的格式不一样。
静态库
在Windows中被称为“静态链接库”。
静态库本质上是一个库。在Linux中一般为.a文件,在Windows中为.lib文件。
假设若干个可执行文件在链接时使用了相同的静态库。实际上,静态库产生的二进制编码被放进了可执行文件中。当这些可执行文件运行的时候,每个可执行文件产生的进程,在内存中各有一份上述二进制编码的副本。
静态链接库的使命在链接完成后就结束了。
同一个可执行文件产生的进程在内存中共用一份代码段。这是Linux操作系统内存管理的特性,与静态库无关。
共享库
在Windows中被称为“动态链接库”。
共享库本质上是一个库。在Linux中一般为.so文件,在Windows中为.dll文件。
假设若干个可执行文件在链接时使用了相同的共享库。实际上,共享库被可执行文件所引用,其二进制编码并没有被放进可执行文件中。当这些可执行文件运行时,被不同可执行文件使用的、同一个由共享库产生的二进制编码,在内存中只有一个副本。这个副本由所有可执行文件的所有进程共用。
静态库和共享库的格式不同,两者的大小不具有可比性。
编译与链接
编译
过程:由源文件生成中间代码文件。
命令:gcc
选项:-c
打包静态库
过程:用中间代码文件生成静态库。
命令:ar
选项:-r
打包共享库
过程:用中间代码文件生成共享库。
命令:gcc
选项:-shared -fPIC
注意:
- 在编译中间代码文件时和打包共享库时,都必须添加上述选项!否则打包会出错。
- 若不加
-fPIC
,则生成“伪共享库”。“伪共享库”既不会被放入可执行文件,又不能被多个引用其的可执行文件共享。
静态链接
过程:用中间代码文件和静态库(可无)生成可执行文件。
结果:可执行文件包含运行所需要的全部二进制代码(包括被调用的系统函数的二进制代码)。可执行文件一般可以独立运行。
命令:gcc
选项:-static
动态链接
过程:用中间代码文件、静态库(可无)和共享库(可无)生成可执行文件。
结果:
- 可执行文件包括中间代码文件和静态库中的二进制代码,这些二进制代码来源于:a.基本语句;b.自定义函数的实现。
- 可执行文件依赖于:链接时使用到的共享库(所有层次的,全部的)。
- 可执行文件一般不能独立运行。
命令:gcc
选项:无
其他
静态编译 = 编译 + 静态链接
动态编译 = 编译 + 动态链接
可以使用ldd命令来查看可执行文件所依赖的共享库。
gcc命令的其他常用选项:
-Wl,-rpath,./
:可执行文件运行时,将所在目录加入共享库的查找范围。-include
:查找头文件并加入编译。-I
(大写的i):将目录的所有头文件加入编译。-l
(小写的L):查找库并加入编译(后面紧跟库的缩写)。-l
:将目录加入库的查找范围。应将-l
放在gcc命令的最后,否则某些时候会出错。
GCC会自动地将一些头文件和库加入编译和链接:
- 头文件的默认查找目录:当前目录、
/include/
、/usr/include/
- 库的默认查找目录:当前目录、
/lib/
、/usr/lib/