怎么制作静态库和共享库 C++/C

【摘要】 静态库和共享库(动态库)
假设有一个库文件,大小为50M,有100个源文件需要调用它,这里统一源文件的大小为10k
对于静态库,每个源文件需要开辟出静态库的空间,那么总共需要的空间大小就是(10k + 50M) * 100; 对于动态库,每个文件都是使用同一个共享库,只需要有库本身的空间即可(10k×100) + 50M
那么动态库岂不是能够乱杀静态库。no…

静态库和共享库(动态库)

假设有一个库文件,大小为50M,有100个源文件需要调用它,这里统一源文件的大小为10k

  • 对于静态库,每个源文件需要开辟出静态库的空间,那么总共需要的空间大小就是(10k + 50M) * 100;

  • 对于动态库,每个文件都是使用同一个共享库,只需要有库本身的空间即可(10k×100) + 50M

那么动态库岂不是能够乱杀静态库。nonono,并不是。

假设库里面有三个函数,当从静态库调用这三个函数时,它是等效与调用源文件本身的函数的,而源文件从动态库调用这三个函数时,由于它并不在源文件内部,所以需要加载进源文件,这个过程则需要时间。

总结下来,就是

  • 对于时间要求比较严格,空间要求较低—-静态库
  • 对于空间要求比较严格,时间要求较低—-动态库

制作静态库

ar rcs libmylib.a file1.o

  
 

库名规定

  • 必须要lib开头
  • 静态库必须.a结尾

现在简述一下创建静态库的过程

1. 创建一个目录 (非必要)

mkdir staticLib
cd staticLib

  
 

2. 在该目录下创建三个c文件

//add.c
int add(int a,int b){ return a+b;
}

  
 
//div1.c
int div1(int a,int b){ return a/b;
}

  
 
//sub.c
int sub(int a, int b){ return a-b;
}

  
 

3. 生成.o文件

gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c div1.c -o div1.o

  
 

4. 制作静态库

ar rcs libmymath.a add.o sub.o div1.o

  
 

5. 使用静态库

  1. 创建test.c
#include<stdio.h>
int main(){ int a =10, b =20; printf("%d + %d = %d \n", a,b,add(a,b)); printf("%d / %d = %d \n", a,b,div1(a,b)); printf("%d - %d = %d \n", a,b,sub(a,b));
}

  
 
  1. 编译该文件
gcc test.c libmymath.a -o test

  
 

事实上编译完文件会出现这样的警告

test.c:4:35: warning: implicit declaration of function ‘add’ [-Wimplicit-function-declaration] 4 | printf("%d + %d = %d \n", a,b,add(a,b)); | ^~~
test.c:5:35: warning: implicit declaration of function ‘div1’ [-Wimplicit-function-declaration] 5 | printf("%d / %d = %d \n", a,b,div1(a,b)); | ^~~~
test.c:6:35: warning: implicit declaration of function ‘sub’ [-Wimplicit-function-declaration] 6 | printf("%d - %d = %d \n", a,b,sub(a,b)); | ^~~

  
 

这意味着这个函数是编译器隐式声明的,我们没有进行声明。如果我们将int add(int a,int b) 改成void* add(int a, int b )就不是警告而是报错了,那么怎么解决?

  • 自己把声明在test.c文件加上 #includede 声明虽然可以,但不现实。 当我们使用他人的静态库时,是不知道其中的代码的。

  • 所以在制作静态库时,需要把头文件(包括声明)也做出来

//mymath.h
#ifndef __MY_MATH_
#define __MY_MATH_
int add(int a, int b);
int sub(int a, int b);
int div1(int a, int b);
#endif

  
 

强调一下 这几个东西的作用

#ifndef __MY_MATH_
#define __MY_MATH_
----
#endif

  
 

ifndef __MY_MATH_ 如果没有define __MY_MATH_的话执行从该行到endif之间的代码,有的话则不执行。
而在ifndef __MY_MARH_加入了define __MY_MATH_,这意味着在源文件中,即便我多次引入该头文件,实际上还是等于引入一次。

第一次,没有define过,所以执行头文件里的代码。第二次,difine过了,则不执行。

制作动态库

1. 生成.o文件的不同

gcc -c add.c -o add.o -fPIC
gcc -c sub.c -o sub.o -fPIC
gcc -c sub.c -o div1.o -fPIC

  
 

和静态库比较,也就是直接加入了-fPIC

2. 使用gcc -shared 制作动态库

gcc -shared lib库名.so add.o sub.o div1.o

  
 

3. 编译可执行的程序,指定所用的动态库, -l指定库名, -L指定库路径

gcc test.c -o a.out -lmymath -L./lib
# 先前制作动态库的时候的命令 gcc -shared libmymath.so add.o sub.o div1.o

# 该库的路径在lib

  
 

4. 解决可执行程序出错问题

当得到a.out 并去执行时,发现出现了该问题

./test: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory

  
 

为什么?

原因:

  • 链接器 : 工作于链接阶段,工作时需要-l和-L告诉库的位置
  • 动态链接器: 工作与程序运行阶段,工作时需要提供动态库的位置(这就是运行时报错的原因。

解决办法一

实际上,动态链接器都会有几个固定的地址,要指定需要的动态库的地址去执行

export LD_LIBRARY_PATH=./lib
#./lib是我的动态库地址(也就是libmymath.so的目录)

  
 

但是这个方法的缺陷就是换个终端执行就需要重新执行该命令。

想要永久生效的话就需要vi ~/.bashrc去更改配置文件,在里面加入export LD_LIBRATH_PATH=./lib,然后重启终端就会加载进去。

解决办法二

ldd test
# 通过该命令可以查找该文件加载动态库的路径
	linux-vdso.so.1 (0x00007ffc8e5ea000)
	libmymath.so => ./libmymath.so (0x00007f823a3c4000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f823a1bc000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f823a3d0000)

  
 

我们发现动态库除了我们自己的libmymath.so,还有其他的动态库,而这些动态库是系统设置的。

于是我们直接将自己定义的动态库拷贝到系统设置的动态库的路径。

cp libmymath.so /lib
#把 libmymath.so自己制作的动态库放到系统设置的动态库的路径下, 这样就等于白嫖系统设置的路径

  
 
# 验证,如果把但前目录下的./libmymath.so给移到其他目录下, 或者直接删掉,会发现test文件仍能执行

mv ./libmymath.so ../..
rm libmymath.so
./test

  
 

实际上,我们不推荐这么做,因为用户的和系统的东西还是要好好分开。

解决方法三

直接改配置文件

第一步, 打开动态库配置文件,并在其添加我们自己动态库的绝对路径,然后:wq

sudo vi /etc/ld.so.conf

  
 

第二步,使之生效

sudo ldconfig -v

  
 

动态库编译原理

那么原理是什么?(可跳过不看)

先来谈一下c文件内的函数。

现在test.c文件的内容化为一般性来描述

//test.c
int main(){ func1(); func2();
}

  
 

经过gcc -c编译后,变为.o文件,得到其中的地址,假设main为0,func1和func相对于main的偏移地址为100, 200

// test.o
main 0 
func1 main + 100
func2 main + 200

  
 

在链接之后得到test,假设main的地址为1000,那么func1的地址为1000 + 100, func2的地址为1000+200

//test
main 1000
func1 1000 + 100;
func2 1000 + 200;

  
 

之前强调过,只有当c文件调用到动态库里的函数,然后才开始加载内存,分配地址。

我们使用objdump来反汇编这个二进制文件

objdump -dS test

  
 

其中我们来截取一部分的信息来观察

0000000000001149 <main>: 1149:	f3 0f 1e fa endbr64 114d:	55 push   %rbp 114e:	48 89 e5 mov %rsp,%rbp 1151:	48 83 ec 10 sub $0x10,%rsp 1155:	c7 45 f8 0a 00 00 00 	movl   $0xa,-0x8(%rbp) 115c:	c7 45 fc 14 00 00 00 	movl   $0x14,-0x4(%rbp) 1163:	8b 55 fc mov -0x4(%rbp),%edx 1166:	8b 45 f8 mov -0x8(%rbp),%eax 1169:	89 d6 mov %edx,%esi 116b:	89 c7 mov %eax,%edi 116d:	b8 00 00 00 00 mov $0x0,%eax 1172:	e8 80 00 00 00 callq  11f7 <add> 1177:	89 c1 mov %eax,%ecx 1179:	8b 55 fc mov -0x4(%rbp),%edx 117c:	8b 45 f8 mov -0x8(%rbp),%eax 117f:	89 c6 mov %eax,%esi 1181:	48 8d 3d 7c 0e 00 00 	lea 0xe7c(%rip),%rdi # 2004 <_IO_stdin_used+0x4> 1188:	b8 00 00 00 00 mov $0x0,%eax 118d:	e8 be fe ff ff callq  1050 <printf@plt> 1192:	8b 55 fc mov -0x4(%rbp),%edx 1195:	8b 45 f8 mov -0x8(%rbp),%eax 1198:	89 d6 mov %edx,%esi 119a:	89 c7 mov %eax,%edi 119c:	b8 00 00 00 00 mov $0x0,%eax 11a1:	e8 7f 00 00 00 callq  1225 <div1> 11a6:	89 c1 mov %eax,%ecx 11a8:	8b 55 fc mov -0x4(%rbp),%edx 11ab:	8b 45 f8 mov -0x8(%rbp),%eax 11ae:	89 c6 mov %eax,%esi 11b0:	48 8d 3d 5c 0e 00 00 	lea 0xe5c(%rip),%rdi # 2013 <_IO_stdin_used+0x13> 11b7:	b8 00 00 00 00 mov $0x0,%eax 11bc:	e8 8f fe ff ff callq  1050 <printf@plt>
  
 

我们观察到callq就是调用函数的意思,对于静态库add和div1

callq 11f7 <add>  
callq 1225 <div1>

  
 

而对于动态库的printf

callq 1050 <printf@plt>

  
 

动态库是在加载或者运行时才开始链接,所以它的函数的地址分配的时间会比静态库的晚。

文章来源: blog.csdn.net,作者:milaiko,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_45407700/article/details/116605209

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享