0
点赞
收藏
分享

微信扫一扫

【Linux操作系统】--动态库和静态库的制作和使用

单调先生 2022-05-02 阅读 64

目录

动态库和静态库

动静态库的制作

静态库的制作

makefile文件

生成静态库

最后库制作代码

运用静态库

【自己写一可执行程序】:

用gcc命令编译

用makefile文件编译

动态库的制作

形成.o文件

使用动态库

两种库的混合使用


动态库和静态库

在linux中查看库,我们可以使用ldd命令,查看可执行程序,比如上面文件中我们写的mytest可执行程序

[wjy@VM-24-9-centos 30]$ ldd mytest
	linux-vdso.so.1 =>  (0x00007ffeb5562000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fe8a82e7000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fe8a86b5000)

而这个库它存在于一个目录的路径下的,这里是一个软连接,而这个2.17指向的就是一个C的库。查看这个文件可以看到,c库有2156592个文件。所谓的库是系统上真正存在的一个文件,

[wjy@VM-24-9-centos 30]$ ls /lib64/libc.so.6 -l
lrwxrwxrwx 1 root root 12 Nov 23 17:52 /lib64/libc.so.6 -> libc-2.17.so
[wjy@VM-24-9-centos 30]$ ls /lib64/libc-2.17.so -l
-rwxr-xr-x 1 root root 2156592 Oct 14  2021 /lib64/libc-2.17.so

需要注意的一点是,在编写C++程序的时候,C++的程序名只能由三种后缀:.cpp/.cc/.cxx这三种,如果是其他的后缀如.cyy编译就会报错

[wjy@VM-24-9-centos 30]$ touch test.cc
[wjy@VM-24-9-centos 30]$ vim test.cc
[wjy@VM-24-9-centos 30]$ cat test.cc
#include <iostream>

int main()
{
  std::cout<<"hello world!"<<std::endl;
  return 0;
}
[wjy@VM-24-9-centos 30]$ g++ test.cc
[wjy@VM-24-9-centos 30]$ ./a.out
hello world!
[wjy@VM-24-9-centos 30]$ mv test.cc test.cpp
[wjy@VM-24-9-centos 30]$ g++ test.cpp
[wjy@VM-24-9-centos 30]$ ./a.out
hello world!
[wjy@VM-24-9-centos 30]$ mv test.cpp test.cxx
[wjy@VM-24-9-centos 30]$ g++ test.cxx
[wjy@VM-24-9-centos 30]$ ./a.out
hello world!

[wjy@VM-24-9-centos 30]$ mv test.cxx test.cyy
[wjy@VM-24-9-centos 30]$ g++ test.cyy
test.cyy: file not recognized: File format not recognized
collect2: error: ld returned 1 exit status
[wjy@VM-24-9-centos 30]$ 

当我们使用静编译,编写makefile文件的时候,在后面要加-static选项。

 默认gcc动态链接编译:在上面我们写过一个test.c代码是c语言的,并且它的编译方式是gcc -o $@ $^,通过file操作可以发现,它是动态编译的。

gcc静态链接编译:如果编译静态链接,用file查看它是静态的链接。如果静态编译出现这样的错误:/usr/bin/ld: cannot find -lc,那么输入这个命令:sudo yum install glibc-static,就可以搞定啦。我们同时也发现,静态链接的体积非常大。

那为什么有时候会报错呢?因为一般服务器,可能美欧内置语言的静态库,而只有动态库。

 动态库和静态库的区别

实际上我们在代码中运用库函数,如C中的printf,我们需要通过链接库的方式,那么链接库的方式有两种,一种是静态的,一种是动态的。

静态库一般就是把库中的内容拷贝进可执行程序,这样如果拷贝到执行程序,执行城区之后的代码将不再依赖库,同时也造成了可执行程序非常大。

而动态库,只是把要关联的函数关联起来,不会进行拷贝的行为,当需要执行库函数代码,我们直接跳转到库当中就可以执行了。那么动态库是怎么实现每个程序都可以用呢,就是通过地址空间和动态库的映射,实现共享,所以我们通过file查看的动态库可以看到上面写着share共享。所以动态库它生成的体积小,但是可移植性是存在问题的,如果库缺失了,那么对应的函数就找不到了。而静态库最然体积大,但是不依赖第三方库,哪怕删掉也没有关系。

动静态库的制作

库本身就是二进制的文件,我们如何得知一个库给我们提供了什么方法呢?

静态库的制作

先在当前目录下新建一个目录test_lib保存我们自己写的库文件。其中我们写两个.c文件和两个.h文件

[wjy@VM-24-9-centos test_lib]$ ll
total 16
-rw-rw-r-- 1 wjy wjy 60 May  1 15:49 add.c
-rw-rw-r-- 1 wjy wjy 66 May  1 15:49 add.h
-rw-rw-r-- 1 wjy wjy 61 May  1 16:42 sub.c
-rw-rw-r-- 1 wjy wjy 66 May  1 16:42 sub.h

[wjy@VM-24-9-centos test_lib]$ cat add.h
#pragma once 
#include <stdio.h>

extern int my_add(int x,int y);
[wjy@VM-24-9-centos test_lib]$ cat add.c
#include "add.h"

int my_add(int x,int y)
{
  return x+y;
}
[wjy@VM-24-9-centos test_lib]$ cat sub.h
#pragma once 
#include <stdio.h>

extern int my_sub(int x,int y);
[wjy@VM-24-9-centos test_lib]$ cat sub.c
#include "sub.h" 

int my_sub(int x,int y)
{
  return x-y;
}

上一级目录写可执行程序mytest.c文件,包含了我们刚刚写好的两个自定义文件。

[wjy@VM-24-9-centos test_lib]$ cd ..
[wjy@VM-24-9-centos 30]$ cat mytest.c
#include "./test_lib/sub.h"
#include "./test_lib/add.h"

int main()
{
  int x=10;
  int y=20;

  int r1=my_add(x,y);
  int r2=my_sub(x,y);

  printf("+:%d\n",r1);
  printf("-:%d\n",r2);

  return 0;
}

当我们直接编译写好的mytest.c文件时,发现找不到包含的两个头文件里面的.c文件,并且这两个文件也没有编译。

所以编译需要加上另外两个.c文件,才能形成a.out文件变成可执行程序。

 但是这样gcc编译太过繁琐,我们可以通过makefile文件编译,使编译过程简单化。

makefile文件

[wjy@VM-24-9-centos 30]$ cat makefile
obj=mytest.o add.o sub.o

mytest:$(obj)
	gcc -o $@ $^
%.o:%.c
	gcc -c $<
%.o:./test_lib/%.c
	gcc -c $< 

.PHONY:clean
clean:
	rm -f *.o mytest 

[wjy@VM-24-9-centos 30]$ make
gcc -o mytest mytest.o add.o sub.o

生成静态库

所以当我们不想给别人看自己的代码,但是必须给别人用,那么我们可以打包静态库,上面的makefile就是打包静态库的一部分,先生成.o文件,最后将.o文件链接在一起,生成可执行文件。所以如果只给了所有的.o文件,我们也可以链接在一起。

如果将所有.o文件打包一下,形成可执行文件,就形成了库。我们可以在test_lib目录里,将.o文件都打包起来,用的是ar命令。

所以在test_lib这样写makefile来打包库文件

[wjy@VM-24-9-centos test_lib]$ cat makefile
libmymuth.a:sub.o add.o
	ar -rc $@ $^
%.o:%.c
	gcc -c $<

值得注意的是libmymuth.a:sub.o add.o这句话是将所有.o文件归档为libmymuth.a自定义命名文件。libmymuth.a这个自定义命名文件就是我们的静态库。在这里make在生成文件的时候会先执行第一句能被生成的文件,.o文件还没有被生成,所以会默认先执行下面的%.o:%.c,生成.o后才能执行libmymuth.a:sub.o add.o,在下面的执行语句中我们也可以看出来。所以第一句话不能写成libmymuth.a:%.o,因为.o文件不明确,它也不会执行下一句话。

make执行.c先生成.o文件,然后将.o文件打包成libmymuth.a,这里libmymuth.a就是静态库!

[wjy@VM-24-9-centos test_lib]$ make
gcc -c sub.c
gcc -c add.c
ar -rc libmymuth.a sub.o add.o
[wjy@VM-24-9-centos test_lib]$ ll
total 32
-rw-rw-r-- 1 wjy wjy   60 May  1 15:49 add.c
-rw-rw-r-- 1 wjy wjy   66 May  1 15:49 add.h
-rw-rw-r-- 1 wjy wjy 1240 May  1 18:06 add.o
-rw-rw-r-- 1 wjy wjy 2694 May  1 18:06 libmymuth.a
-rw-rw-r-- 1 wjy wjy   57 May  1 17:57 makefile
-rw-rw-r-- 1 wjy wjy   61 May  1 16:42 sub.c
-rw-rw-r-- 1 wjy wjy   66 May  1 16:42 sub.h
-rw-rw-r-- 1 wjy wjy 1240 May  1 18:06 sub.o

查看静态库文件:

[wjy@VM-24-9-centos test_lib]$ ar -tv libmymuth.a 
rw-rw-r-- 1001/1001   1240 May  1 18:06 2022 sub.o
rw-rw-r-- 1001/1001   1240 May  1 18:06 2022 add.o

【将库文件打包给其它人】:

当我们要把库文件给别人使用,需要给别人.h文件和已经编译好的.o文件,刚刚我们已经将.o文件打包好了。但是我们怎样通过一个make命令就将.o和.h文件一起打包好呢?

我们在刚才的makefile文件继续追加.PHONY命令。在命令里面新建一个目录,将.h和归档的.o文件一起拷贝到output目录里面。然后make output命令执行以下,发现生成了output目录而且里面有所有的.h和.o文件。

.PHONY:output
output:
	mkdir output 
	cp -rf *.h output 
	cp libmymuth.a output 
[wjy@VM-24-9-centos test_lib]$ make output
mkdir output 
cp -rf *.h output 
cp libmymuth.a output 
[wjy@VM-24-9-centos test_lib]$ ll
total 36
-rw-rw-r-- 1 wjy wjy   60 May  1 15:49 add.c
-rw-rw-r-- 1 wjy wjy   66 May  1 15:49 add.h
-rw-rw-r-- 1 wjy wjy 1240 May  1 18:06 add.o
-rw-rw-r-- 1 wjy wjy 2694 May  1 18:25 libmymuth.a
-rw-rw-r-- 1 wjy wjy  181 May  1 18:24 makefile
drwxrwxr-x 2 wjy wjy 4096 May  1 18:25 output
-rw-rw-r-- 1 wjy wjy   61 May  1 16:42 sub.c
-rw-rw-r-- 1 wjy wjy   66 May  1 16:42 sub.h
-rw-rw-r-- 1 wjy wjy 1240 May  1 18:06 sub.o
[wjy@VM-24-9-centos test_lib]$ ls output
add.h  libmymuth.a  sub.h

 扩展知识:把库安装到系统当中

.h和libmymuth.a文件拷贝到系统C库文件下,这样就变成系统文件了。.h的声明文件放在/usr/include目录下,.o可执行文件放在/lib64目录下。

.PHONY:install
install:
	cp *.h /usr/include 
	cp libmymuth.a /lib64 

最后库制作代码

[wjy@VM-24-9-centos test_lib]$ cat makefile
libmymuth.a:sub.o add.o
	ar -rc $@ $^
%.o:%.c
	gcc -c $<

.PHONY:clean
clean:
	rm -rf output libmymuth.a

.PHONY:output
output:
	mkdir output 
	cp -rf *.h output 
	cp libmymuth.a output 
[wjy@VM-24-9-centos test_lib]$ tree output
output
|-- add.h
|-- libmymuth.a
`-- sub.h

0 directories, 3 files

最后我们只需要给别人交付这个output目录文件就可以啦。

运用静态库

当别人把.o编译文件和.h 库给我们,我们先将它拷贝到自己的目录下,并重新命名。

friend就是我们的目录,拷贝到friend目录下后,重命名为lib,查看这个目录,确实拷贝过来了。

[wjy@VM-24-9-centos 30]$ cp test_lib/output friend/lib -rf
[wjy@VM-24-9-centos 30]$ cd friend
[wjy@VM-24-9-centos friend]$ ll
total 4
drwxrwxr-x 2 wjy wjy 4096 May  1 18:58 lib
[wjy@VM-24-9-centos friend]$ tree lib
lib
|-- add.h
|-- libmymuth.a
`-- sub.h

0 directories, 3 files

【自己写一可执行程序】:

[wjy@VM-24-9-centos friend]$ touch test.c
[wjy@VM-24-9-centos friend]$ ll
total 4
drwxrwxr-x 2 wjy wjy 4096 May  1 18:58 lib
-rw-rw-r-- 1 wjy wjy    0 May  1 19:51 test.c
[wjy@VM-24-9-centos friend]$ vim test.c
[wjy@VM-24-9-centos friend]$ cat test.c
#include "add.h"
#include "sub.h"

int main()
{
  int x=10;
  int y=20;
  int r1=my_add(x,y);
  int r2=my_sub(x,y);

  printf("%d\n",r1);
  printf("%d\n",r2);

  return 0;
}

用gcc命令编译

当我们gcc编译的时候,发现它给我们报错了,所以还要改进gcc命令。

第一次编译发现找不到头文件。当我们编译的时候,编译器找不到头文件的。是因为test.c文件和头文件不在同级目录。test.c找头文件要在同级中找头文件。所以gcc要加一个选项-I./lib

 当我们加上-I./lib选项后,又报错。这是一个链接报错,原因是编译器找不到库在哪里。但是我们的库不是在lib里面吗,libmymath.a就是我们的库呀。 这是因为编译器找库文件还是在同级文件下查找,所以还要加一个-L./lib选项来找库文件。

 但是当我们添加-L./lib选项后,编译还是报错,并且报错内容和上面的一样。在我们test.c文件中要包含头文件,是将头文件的名字写在文件里的。那么我们要查找库的时候,要在gcc编译命令上明确写上库的名称,有的人有疑问了,在lib目录下不是只有一个唯一的库文件吗,为什么没有直接找这个库文件?这是因为gcc命令它不认识lib目录下库文件的名称,如果想要知道库文件名称,它的真正名字我们在上面提到,要去掉lib前缀,和.a后最才是最后的名字。所以在gcc命令后面还要跟一个选项-l库名称,也就是-lmymuth(有没有空格都可以)。

 所以最后编译的命令是gcc test.c -I./lib -L./lib -lmymuth才能编译通过。

-I (大i)指定头文件搜索路径

-L指明库文件搜索路径

-l(小L)指明要链接哪一个库

我们之前写的代码,也用了库,为什么没有指明这些选项呢?

因为之前的库,在系统的默认路径下:在./lib64(云服务器下)或者./usr/lib,/usr/include等,编译器是能识别这些路径的。所以说,如果我们不想带这些选项,我是不是可以把对应的库和头文件拷贝到默认路径下,但是并不推荐这个方法,万一我们的命名不规范,或者命名冲突,会把原有文件覆盖。第二会污染别人的库命令池。

上面的过程,就是一般的软件安装。

用makefile文件编译

当我们知道了gcc命令的编译方式,我们用make工具使编译简便化。编辑makefile文件后,make进行编译生成可执行文件mytest,再运行,编译成功,运行结果正确。

[wjy@VM-24-9-centos friend]$ cat makefile
mytest:test.c
	gcc -o $@ $^ -I./lib -L./lib -lmymuth
.PHONY:clean
clean:
	rm -f mytest 

//运行结果
[wjy@VM-24-9-centos friend]$ make
gcc -o mytest test.c -I./lib -L./lib -lmymuth
[wjy@VM-24-9-centos friend]$ ./mytest
30
-10

当我们用ldd查看mytest可执行文件所用的库,发现没有我们自己写的libmymuth.a静态库呀。这是因为静态库的程序已经拷贝在了我们的可执行文件mytest中了。

动态库的制作

动态库的制作与静态库的制作基本相似,我们还是用上面test_lib目录里面的头文件和.o文件。但是形成库的makefile文件要改变(test_lib就是我们写自定义库的地方)。

形成.o文件

动态库的形成还是先形成.o文件,但是这里需要加一个选项-fPIC,这个选项就是形成与位置无关码

[wjy@VM-24-9-centos test_lib]$ cat makefile
#动态库
#形成一个动态链接的共享库
libmymath.so:add.o sub.o
	gcc -shared -o $@ $^
#产生.o目标文件,程序内部地址方案是:与位置无关,库文件可以存在内存的任何位置加载,而且不影响其它程序的关联性。
%.o:%.c
	gcc -fPIC -c $<

.PHONY:clean 
clean:
	rm -f libmymath.so 

.PHONY:lib 
lib:
	mkdir lib 
	cp *.h lib 
	cp libmymath.so lib 

在这里先形成.o文件,以为是动态链接所以要加-fPIC这个选项。为什么要加这个选项呢?最初这个动态链接的库文件是在磁盘中的,当加载到内存中时,它的位置也被记录下来。如果每次都要加载库文件,就得修改一次位置,它的可执行程序也要修改。地址加载到内存怎么做到在任意位置加载?产生.o目标文件,程序内部地址方案是:与位置无关,库文件可以存在内存的任何位置加载,而且不影响其它程序的关联性

什么叫与位置无关码?举个例子这里有A和B两个人,还有一颗树。A和B的距离是100m,B和树的距离是10m.A告诉B我离你100m;之后A又向前走了20m,此时刚才对B说的我离你100m已经不起作用,这种情况A和B的距离是和A有关的。但是我们换种说法,A开始离这个树有110m,后来A离这棵树90m,这个时候A再怎么变化,B的距离是不发生改变的,A和B的距离与树有关。这就可以类比到与位置无关码,无论A在什么位置都不影响B的位置。这也是采用偏移量,相对地址方案,而不是绝对编址,如果是绝对编址,程序加载到内存,程序地址就失效了。而相对地址可以随便加载。

这里将.o文件打包,不再采用静态库中的ar,而是gcc命令。但是与以往直接写gcc -o $@ $^不同,我们还要在其中加入share,因为文件在编译的时候要变成静态库,要形成一个动态链接共享库:gcc -shared -o $@ $^。这样看来动态库的写法比静态库容易的多。

最后我们将动态库libmymath.so和.h文件打包一下,一起打包到lib目录中。

这时候再make以下,用file命令可以查看到它是一个动态库。


最后进入到我们自己要写程序的friend目录下,将别人的动态库拷贝到我们自己目录下。用ll查看。

[wjy@VM-24-9-centos friend]$ cp ../test_lib/lib . -rf
[wjy@VM-24-9-centos friend]$ ll
total 24
-rwxrwxr-x 1 wjy wjy 8480 May  1 20:47 a.out
drwxrwxr-x 2 wjy wjy 4096 May  1 23:29 lib
-rw-rw-r-- 1 wjy wjy   88 May  1 21:45 makefile
-rw-rw-r-- 1 wjy wjy  174 May  1 19:55 test.c

使用动态库

根据静态库的使用方法,我们很快就能写出一个makefile文件,无非就是找头文件,找动态库,找动态库的名字,加上了一系列选项。但是仅仅这样编译,它会报错。

 通过ldd发现,mytest中找不到别人给的libmymath.so库。这是因为当程序编译好的时候,此时已经和编译器无关,gcc -o $@ $^ -I./lib -L./lib -lmymath这句代码之事告知编译器,头文件,库文件的路径在哪里。当执行可执行文件./mytest的时候,需要用到加载器,但此时加载器并不知道库和头文件在哪里,在执行可执行文件的时候,需要进一步告知加载器,我们的库文件来自哪里。

 为什么静态库没这个加载器问题,是因为静态库已经将文件都拷贝在了可执行程序里,一下就找到了。但是文件在执行可执行运行起来时,还不知道库在哪里。

加上这个加载搜做路径,再次编译文件,成功。

【总结】:

在我们写程序的时候,其实我们一直都在直接或间接使用库(C/C++)。如果要写一个库

  • 静态库:ar -rc
  • 动态库:gcc -fFIC -shared

当拿到别人的库和偷偷文件,加入到自己的项目中,如何制作:

  1. 先把自己的所有源文件编译成.o
  2. 制作动静态库的本质:将所有.o打包,使用ar或gcc来进行打包
  3. 交付:.h + .a/.so文件

两种库的混合使用

如果只提供静态库,我们只能将库静态链接到我们的程序中。

如果只提供动态库,我们只能将库动态链接到程序当中。并且动态链接情况下后面不能加-static选项变成静态链接。如果此时只有动态库,我们是无法-static静态链接的!

 如果既想使用动态链接又想使用静态链接,一般需要提供两种版本的库文件。

在gcc和g++中,静态和动态优先链接哪一个呢?优先的是动态链接。

举报

相关推荐

0 条评论