怎么制作静态库和共享库 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. 使用静态库

  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文件加上 — 虽然可以,但不现实。 当我们使用他人的静态库时,是不知道其中的代码的。

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

//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的主要内容,如果未能解决你的问题,请参考以下文章

C语言中静态库和动态库的区别,如何使用它们

linux下静态库和动态库的制作

Linux 静态库和共享(动态)库的创建与使用详解

关于动态库和静态库的问题。

共享库和静态库的使用区别

C语言Linux下动态库和静态库详解