怎么制作静态库和共享库 C++/C
Posted milaiko
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了怎么制作静态库和共享库 C++/C相关的知识,希望对你有一定的参考价值。
静态库和共享库(动态库)
假设有一个库文件,大小为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. 使用静态库
- 创建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));
}
- 编译该文件
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文件加上 — 虽然可以,但不现实。 当我们使用他人的静态库时,是不知道其中的代码的。
-
所以在制作静态库时,需要把头文件(包括声明)也做出来
//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>
动态库是在加载或者运行时才开始链接,所以它的函数的地址分配的时间会比静态库的晚。
以上是关于怎么制作静态库和共享库 C++/C的主要内容,如果未能解决你的问题,请参考以下文章