动态链接和静态链接
c/c++源文件要经过预处理、编译、汇编、链接才能形成可执行程序.
在c/c++中,以c语言标准库为例:.h头文件一般存放方法的声明,而方法的实现会放在库中.
其中链接分为两种:
静态链接:将库中方法的具体实现,拷贝到我们的可执行程序中,此时程序的运行不需要依赖库,但是体积会变大.
动态链接:将库的地址,拷贝到我们的可执行程序中,程序运行要依赖动态库,程序运行时,需要到动态库里去找函数的实现.
gcc/g++默认是动态链接,如果要静态链接,加上选项-static
静态链接的文件大小远远大于动态链接的文件
ldd可以查看可执行程序链接的动态库
linux: .so(动态库) .a(静态库)
windows: .dll(动态库) .lib(静态库)
制作静态库
静态库的本质:把所有.o文件打包,就是形成静态库
test1.h
test1.c
test2.h
test2.c
ar -rc lib库名.a 要打包的全部.o文件
ar ------ 归档 全称archive
r ------ 替换
c ------ 创建
lib自定义库名.a ------ 前缀lib固定,.a是静态库的后缀
例如:makefile自动化构建静态库,libstatic.a就是生成的静态库,打包了test1.o和test2.o的代码
makefile
libstatic.a:test1.o test2.o
ar -rc libstatic.a test1.o test2.o
test1.o:test1.c
gcc -c test1.c -o test1.o
test2.o:test2.c
gcc -c test2.c -o test2.o
.PHONY:clean
clean:
rm -f libstatic.a test1.o test2.o
一般而言,发布一个写好的库,需要包含头文件(方法列表)和库文件(方法实现),目录结构如下:
优化后的makefile如下:
makefile
libstatic.a:test1.o test2.o
ar -rc libstatic.a test1.o test2.o
test1.o:test1.c
gcc -c test1.c -o test1.o
test2.o:test2.c
gcc -c test2.c -o test2.o
.PHONY:static
static:
mkdir -p static/include
mkdir -p static/lib
cp test1.h test2.h static/include
cp libstatic.a static/lib #生成库的目录结构
.PHONY:clean
clean:
rm -rf static libstatic.a test1.o test2.o
如何使用静态库
一、将库的头文件和库文件拷贝到系统路径下
将库的头文件和库文件,拷贝到系统路径下,就是库的安装
对于gcc/g++,头文件的默认搜索路径是:/usr/include
库文件的默认搜索路径是:/lib64 或/usr/lib64
拷贝完成效果
main.c要使用static库的方法
arduino
//main.c
#include <test1.h>
#include <test2.h>
int main()
{
//都是static库的方法
test1();
test2();
return 0;
}
static是第三方库,尽管已经被拷贝进系统路径,也必须手动让gcc/g++链接
gcc main.c -o main -lstatic
二、直接链接第三方库,不拷贝到系统路径下
gcc main.c -I ./static/include -L ./static/lib -lstatic
-I:头文件搜索路径
-L:库文件搜索路径
-l:在指定路径下,指定使用库名
制作动态库
gcc -fPIC -c test1.c -o test1.o # 形成与位置无关的目标二进制文件
gcc -shared .o文件列表 -o lib动态库名.so
1 理解:与位置有关
静态库形成后,静态库的代码最后是要放进可执行程序内部
可执行程序有自己的地址空间,静态库要链接进来,
静态库的代码和数据的地址,必须在地址空间的特定位置,已经固定.
代码和常量在代码区、局部变量在栈区等
例如:main是静态链接static后形成的可执行程序,
objdump -t main 可以查看程序的符号表.
objdump -t main | grep 'test'
2 理解:与位置无关
动态库将来采用相对地址的方案
链接动态库后,生成可执行程序
只有当进程执行,要访问动态库的方法时,此时才将动态库的代码加载进内存,和当前进程的地址空间通过页表建立映射关系,将动态库的代码映射到 共享区,此时才会确定方法的地址.
如果有很多个进程,动态库只要加载一次,就能被多个进程共同使用,同一份动态库代码,通过页表可以被映射到多个进程地址空间的共享区.
例如:下面的main和上面的不一样,是动态链接生成的可执行程序
3 优化makefile,同时生成动静态库
makefile
.PHONY:all
all:libstatic.a libdynamic.so
libdynamic.so:test1dynamic.o test2dynamic.o
gcc -shared test1dynamic.o test2dynamic.o -o libdynamic.so
test1dynamic.o:test1.c
gcc -fPIC -c test1.c -o test1dynamic.o
test2dynamic.o:test2.c
gcc -fPIC -c test2.c -o test2dynamic.o
libstatic.a:test1.o test2.o
ar -rc libstatic.a test1.o test2.o
test1.o:test1.c
gcc -c test1.c -o test1.o
test2.o:test2.c
gcc -c test2.c -o test2.o
.PHONY:Lib
Lib:
mkdir -p Lib/include
mkdir -p Lib/lib
cp test1.h test2.h Lib/include
cp libstatic.a libdynamic.so Lib/lib
.PHONY:clean
clean:
rm -rf Lib libstatic.a test1.o test2.o libdynamic.so test1dynamic.o test2dynamic.o
如何使用动态库
一、 将动态库拷贝到系统路径中
gcc默认链接动态库
1 如果只有静态库,gcc只能针对该库进行静态链接
2 如果动静态库同时存在,默认用动态库
-static:摒弃默认使用动态库的原则,而是直接使用静态库的方案
二、直接链接动态库
为什么ldd 可执行程序,仍然找不到动态库?
因为运行加载时,和gcc已经没有关系了,链接时只告诉了gcc库的地址,现在应该要告诉 os的加载器 动态库的地址 (如果把库的文件放到系统路径中,是可以直接运行的)
1 设置系统环境变量 LD_LIBRARY_PATH
加载时,库的搜索路径都被放在该环境变量中
可以将动态库所在路径放进该环境变量中
(重新登录后,会在系统配置文件中重新拿LD_LIBRARY_PATH的值,之前设置的环境变量会被重置)
2 修改配置文件,/etc/ld.so.conf.d
在该路径下创建一个文件xxx.conf,用vim打开后,把库路径拷贝进去
ldconfig
让配置文件生效
即使LD_LIBRARY_PATH里没有这个库路径,但仍能正常运行
3 建立软链接
ln -s ./Lib/lib/libdynamic.so /usr/lib64/libdynamic.so
4 用户登录时,系统自动执行脚本.bash_profile,这个脚本里会执行.bashrc,可以导入环境变量
为什么要有库?
1 站在使用库的角度,库的存在,可以大大减少我们开发的周期,提高软件本身质量
2 站在写库的人角度
(1) 简单
(2) 代码安全(代码不公开,源代码安全)