unix_c--静态链接库和动态链接库

编译的过程

当我们用gcc编译c语言程序的时候都是直接生成可执行文件,但是在实际的编译过程中
编译的过程也是有明显的步骤之分的。
以最简单的程序hello.c为例。
1:预编译
gcc -E hello.c -o hello.i
简单来说就是替换掉宏,展开头文件。

1
2
3
4
5
6
将所有的`#define`删除,并且展开所有的宏定义
处理所有条件预编译指令,比如`#if`、`#ifdef`、`#elif`、`else`、`#endif`
处理`include`预编译指令,将被包含的头文件插入到该预编译指令的位置。(这个过程是递归进行的,也就是被包含的文件可能包含有其他的文件)
删除所有的注释`//`和`/**/`
添加行号和文件名标识,比如`#2"hello.c"`,以便于编译时遍一起产生调试用的行号信息以及用于编译时产生编译错误或警告时能够显示行号
保留所有的`#progma`编译指令,因为编译器须要使用它们

2:编译
gcc -S hello.i -o hello.s
简单来说是将c语言程序编译成汇编代码。

1
2
编译过程就是把预处理完的文件进行一系列的
词法分析、语法分析、语义分析以及优化后产生向以的汇编代码文件。

3:汇编
简单来说汇编是将汇编代码转换成可重定向的二进制文件。
gcc -c hello.s -o hello.o

1
2
3
4
5
6
汇编是用汇编器将汇编代码转变成机器可以只行的指令,
每一个汇编预言语句几乎都对应一条机器指令。
所以汇编器的汇编过程相对于编译器来讲比较简单,
它没有复杂的语法,也没有语义,也不需要做指令优化,
值是根据汇编指令和机器指令的对照表一一翻译就可以了,
`汇编这个名字也来源于此`。

4:链接
简单来说就是将可重定向文件与库文件链接生成可执行的二进制文件。

1
2
3
链接也有两种方式:
一种是与静态库的链接
一种是与动态库的链接

本文主要是来区分两种链接方式的不同用法

链接

首先看以下所需要的几个文件
hello.h

1
2
3
4
5
6
#ifndef _HELLO_H
#define _HELLO_H

void pirnt();

#endif

hello.c

1
2
3
4
5
6
7
#include <stdio.h>
#include "hello.h"

void print()
{

printf("hello,world\n");
}

main.c

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include "hello.h"

int main(int argc, char **argv)
{

print();

return 0;
}

无论时静态库还是静态库,都是由.o文件打包形成的
前面的预编译和编译就省略,直接到汇编生成可重定向的二进制文件

1
2
3
4
[root@linux demo]# gcc -c hello.c
[root@linux demo]# gcc -c main.c
[root@linux demo]# ls
hello.c hello.h hello.o main.c main.o

链接操作
这里是直接用的是gcc的链接

1
2
3
4
5
[root@linux demo]# gcc hello.o main.o -o main
[root@linux demo]# ls
hello.c hello.h hello.o main main.c main.o
[root@linux demo]# ./main
hello,world

静态库链接的运作方式

静态库一般是以.a结尾,lib开头。
实际上呢,静态库就是对汇编生成的可重定向的二进制文件的XXX.o文件的打包
打包是为了提高代码的利用率?
那么如何将.o文件打包成静态库同时在链接的时候使用静态库呢?
比如hello.o文件,当其他程序要使用hello.o中的函数时,把hello.h文件包含进去

1
2
3
4
5
6
7
8
9
10
#将hello.o打包成静态库
[root@linux demo]# ar cr libhello.a hello.o
#显示libhello.a里面包含的文件
[root@linux demo]# ar t libhello.a
hello.o
[root@linux demo]# gcc -o main main.c libhello.a
[root@linux demo]# ls
hello.c hello.h hello.o libhello.a main main.c main.o
[root@linux demo]# ./main
hello,world

动态链接的运作方式

动态库一般是以.so结尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#先把hello.c打包成动态库libhello.so
[root@linux demo]# gcc -fpic -shared hello.c libhello.so
#将动态库与main.c一起编译成main
[root@linux demo]# gcc libhello.so main.c -o main
#此时libhello.so并没有被加载到内存,所有不能运行
[root@linux demo]# ./main
./main: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
[root@linux demo]# ldconfig libhello.so
ldconfig: relative path libhello.so used to build cache
#ldconfig +绝对路径 将路径下的所有动态库都加载进去
[root@linux demo]# ldconfig $(pwd)
#加在完成,程序就可以执行了
[root@linux demo]# ./main
hello,world

使用静态库和动态库的优缺点

其实优缺点从他们使用的方式就体现了出来
静态库时在链接的时候,把库的代码都链接了过来,这样静态链接的程序可以不依赖于
库从而可以独立运行,而动态链接的程序,当它所需要的库还没有加载到内存的时候是
不能够运行的。
静态库每次都要把代码复制到程序中,这样程序本身就需要占用磁盘空间,在程序
运行的时候也是非常消耗内存的,如果有很多程序都使用了相同的库,那样就更是浪费
了。
而动态库的链接只是引用了动态库的符号表,并不会把其他的并入到程序中。
同时程序在运行时,使用了相同动态库的程序会共用那个动态库。
但是,在系统开始运行使用了动态链接的程序时,系统会先把控制权交给动态链接器,
由它完成所有的动态链接过程工作之后再把控制权交给程序。这样缺点也显而易见了,
执行过程慢,程序在运行前还要先调用动态链接器。

总结:
静态库:
优点:执行速度快,编译好之后不需要依赖其它库就可以运行
缺点:浪费空间,程序存放时会占用硬盘空间,执行的时候会占用内存
动态库:
优点:节省空间,可以实现多个程序在运行时使用同一个库,节省磁盘空间和内存
缺点:执行速度较慢,在程序的时候要先执行动态链接操作。

Contents
  1. 1. 编译的过程
  2. 2. 链接
    1. 2.1. 静态库链接的运作方式
    2. 2.2. 动态链接的运作方式
    3. 2.3. 使用静态库和动态库的优缺点
,