# 概念 我们编写的文件,需要经过一定的处理才能转换成机器上可运行的可执行文件。我们将对C语言的这种处理过程称为编译与链接。 编译就是把文本形式源代码翻译为机器语言形式的目标文件过程。 链接是把目标文件、操作系统的启动代码和用到的库文件进行组织最终形成可执行代码的过程。 # 实验 对下面的代码进行编译 ```c #include <stdio.h> #include <stdlib.h> int e = 10; int hello(); int main(int argc, char const *argv[]) { int a[3]; char b[3]; char *c = (char *) malloc(sizeof(char) * 3); char *d = "hello world"; printf("a: %p\n", a); // 栈 printf("b: %p\n", b); // 栈 printf("c: %p\n", c); // 堆 printf("d: %p\n", d); // 只读区(常量区) printf("&d: %p\n", &d); // d 是指针,本身的地址是在栈上 printf("&e: %p\n", &e); printf("main: %p\n", main); // 代码区 printf("hello: %p\n", hello); return hello(); } int hello(){ return 1; } ``` ## 编译 `-c`只编译不链接 ``` $ gcc test.c -c -o test.o $ nm test.o U ___stack_chk_fail U ___stack_chk_guard 000000000000012c D _e 0000000000000120 T _hello 0000000000000000 T _main U _malloc U _printf ``` ## 链接 找到符号,为找到的符号确定地址 ``` $ gcc test.o -o test $ nm test U ___stack_chk_fail U ___stack_chk_guard 0000000100000000 T __mh_execute_header 0000000100001030 D _e 0000000100000f10 T _hello 0000000100000df0 T _main U _malloc U _printf U dyld_stub_binder ``` ## 运行 ``` $ ./test a: 0x7fff5405aa9c b: 0x7fff5405aa85 c: 0x7fb54ac02780 d: 0x10bba5f5e &d: 0x7fff5405aa70 &e: 0x10bba6030 main: 0x10bba5df0 hello: 0x10bba5f10 ``` # 原理 ## 链接 > 摘录于《C专家编程》 ### 静态链接 Unix 早期编译时,将需要使用的每个库函数一份拷贝加入到可执行文件中,也就是**静态链接**。 ### 动态链接 后来一种更为现代和优越的被称为动态链接的方法逐渐被采用。动态链接允许系统提供一个庞大的函数库集合,可以提供许多有用的服务。程序在运行时寻找它们,而不是把这些函数库的二进制代码作为自身可执行文件的一部分。 如果可执行只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库,那么我们称之为**动态链接**。 ### 动态链接的优势 1. 可执行文件体积非常小; 2. 链接-编辑阶段时间也会缩短; 3. 解耦,把程序和它们使用的函数库分离(*关键字ABI*); 4. 提升计算机整理性能 ### 如何提升整理性能 1. 动态链接的可执行文件比功能相同的静态链接可执行文件的体积小,能够节省磁盘空间和虚拟内存,因为函数库只有在需要时才被映射到进程中。 2. 所有动态链接到某个特定函数库的可执行文件在运行时共享该函数库的一个单独拷贝。操作系统内核保证映射到内存中的含数据库可以被使用它的其他进程共享,提供了更好的I/O和交换空间利用率,节省了物理内存,从而提高了系统的整体性能。如果可执行文件是静态链接的,每个文件都将拥有一份函数库的拷贝,显然极为浪费空间(系统调用`mmap`把文件映射到进程的地址空间中)。 ## 静态库和动态库 任何人都可以创建静态或者动态的函数库,源码文件不含有`main`函数。 1. 静态库(`archive`),通过`ar`来创建和更新,以`.a`作为扩展名。过时了,不推荐使用。动态库,通过`ld`来创建和更新,以`.so`作为扩展名。 2. 通过`-lthread`选项告诉编译链接到`libthread.so`,`-l{name}`是`-lib{name}.so`的缩写。 3. 编译器期望在确定的目录找到库,除了系统默认的一些目录外,可以在链接时使用`-Lpathname`和`-Rpathname`来指定。 4. 通过头文件确认使用的库函数 5. 始终将`-l`函数库选项放在编译命令行的最右边