静态库:(如上图左),他的库跟随源文件一起编译到了可执行程序中,所以连接静态库的源文件编译出来的的可执行文件的大小是程序本身的大小+静态库的大小
动态库:(如上图右),他的库不跟随源文件一起编译,而是放在内存中,程序调用时直接从内存调用,其他程序都调用同一个动态库,所以连接动态库的源文件编译出来的可执行文件大小是程序本身的大小(不用加“所调用的函数的那一点点大小”,因为调用函数语句就包含在程序中)
每个程序都要将静态库编译到自己的程序中,所以如果有100个程序,那么静态库所占用的大小就是500*100M
首先写好静态库源文件,里面都是一些函数
然后使用gcc编译,设置参数为-c,表示编译成.o文件
(实际上不需要-o对生成的文件进行命名,因为默认的命名方式生成也是add.o,但是为了保险且为了便于阅读,以及养成良好习惯,还是加上-o,进行一下命名)
之后,利用ar rcs命令进行静态库的制作
语法:ar rcs lib库名.a 原材料文件(想要做出静态库的那些.o文件)
最后会生成lib库名.a文件,这就是制作出来的静态库
我们就这样将源文件与静态库一起编译生成可执行程序后,看似没什么问题
但是如果我们加上了-Wall选项,也就是显示出所有的警告信息,我们会发现会出现警告,那就说明这个程序存在问题
警告信息是“隐形的函数声明”
当一个程序没有函数声明,且函数定义不在main函数上面时,系统会自动给所调用的函数添加声明,只不过这个声明是隐式声明,他的规则如下:
即,系统所生成的声明返回值固定是int 函数名和参数类型都能根据函数调用推断出来
恰巧,我们这个函数原型就是int返回值,但是如果我们的函数不是int返回值,那么就会出现问题,所以,我们不能依赖于系统给我们做函数声明。
实际上,每一个静态库都有自己的头文件,我们只需要在程序里包含静态库的头文件即可(可以对比使用c++的STL库也要包含头文件)
这里我们试着自己做一下头文件:
可以看到头文件有三行宏定义语句,他的功能是“防止头文件重复包含”
原理:第一行是ifndef 注意是n 也就是如果没有定义后面那个宏 那么执行第二条宏语句,这时这个宏就被定义了 就会引入函数的声明
如果此时程序又包含头文件,那么这时宏已经被定义,就不会再进入下面的语句,也就不会再引入函数声明,也就意味着除了第一条包含语句有效,其他的都无效
这也被称为头文件守卫
使用时,要将想要使用库的源文件和库一起进行编译,所以要库文件在-o之前,在源文件后面, 紧跟着源文件,如上图
然后直接命令行调用可执行程序即可运行:
将静态库文件放在lib文件夹,静态库头文件放在inc文件夹(一种规范,大多标准的静态库都会这样存放)
当静态库与源文件不在同一级目录下时,编译时就要明确静态库的目录并精确到静态类本身,如上图./lib/libmymath.a
(生成的可执行文件默认是.out文件,可以对其重命名,上图重命名还是a.out)
这里注意,静态库的头文件与可执行文件不在同一个目录下,这时就要用到-I选项(详解见“系统编程–gcc编译”)
-I选项用于头文件与源文件不在同一个目录下,参数是头文件所在的路径,精确到所在目录即可,无需精确到头文件本身
(如果头文件与源文件在同一个目录下,那么无需任何参数,直接编译源文件即可)
补充:
我们可以看到生成的可执行文件比源文件要大的多,这就是因为生成的可执行程序里面有静态库的加入
动态库是多个程序共享,且不会编译到可执行程序中,所以动态库占用的大小还是本身的500M
我们知道在链接阶段,会进行数据段合并以及地址回填
具体什么是地址回填:如上图右边所示
对于本地函数:
一个main函数里面有一些函数func1 func2,上面是函数定义,这是一个很常规的程序
而当源码向可执行程序转换时,要经过四个阶段(gcc编程四个阶段)
当执行到第三阶段时,生成.o文件,此时main函数的地址还未确定,而函数的地址也是相对于main函数的地址,属于半确定,要等main函数地址确定了才能真正确定
所以,链接阶段,生成.out文件,那么main函数的地址就被确定了,其本地函数的地址也随之被确定了
至此,本地函数的地址就确定了
但是对于动态库函数,他的地址不依赖与main函数地址,而是依赖动态库里面的@plt这个东西,所以某个动态库函数地址在一个程序中的确定,是依赖于动态库的,当动态库函数被调用时,动态库才会被加载进内存,这时,该函数的地址才能被确定
所以,动态库函数的地址的确定要比其他本地函数地址的确定要晚一点,也被称为地址延迟绑定
有了上面的“动态库函数依赖于@plt”的理解,我们在生成.o文件时,要额外加上一个选项 -fPIC
他的作用就是将函数地址依赖于@plt
首先制作.o文件
然后利用gcc -shared来制作动态库
语法:gcc -shared -o lib库名.so 原材料(一些.o文件)
注意这里与静态库不同的是,要在库名前加-o
头文件还是一些函数声明+头文件守护
然后具体编译语法:gcc 源文件 -o 对生成的可执行文件命名 -l +库名(除去lib以及.so) -L +库所在的路径(精确到所在目录即可)
如果头文件与源文件不在同一个目录,那么需要加-I 选项,+头文件所在路径(精确到目录)
我们使用sp 或者 vsp 然后直接跟上一个文件名,这个文件名不存在的话,会帮助你创建出来这个文件并且分屏打开
当提示信息显示行号,那么就是编译阶段出错
当提示信息不显示行号,那么就是链接时出错