目录
下面,我们先试着做一下静态库:
首先,我们再创建一个 /user/lib 的目录文件。
再改目录文件里面写一批接口,其中包括 .h 文件和实现 .c 文件。
myPrint
- // myprint.h
- #include
- #include
-
-
- extern void Print(const char* str);
- // myprint.c
- #include"myprint.h"
-
- void Print(const char* str)
- {
- printf("%s: [%ld]\n", str, time(NULL));
- }
myMath
- // mymath.h
- #include
-
- extern int sum(int from, int to);
- // mymath.c
- #include"mymath.h"
-
- int sum(int from, int to)
- {
- int count = 0;
- for(int i = from; i <= to; ++i)
- {
- count += i;
- }
- return count;
- }
之前我们说了,当我们编译文件的时候,只需要将源文件编译为 .o 文件就好了。
也就是将 mymath.c 和 myprint.c 编译为 .o 文件。(gcc -c ....) 就是编译为 .o 文件。
- [lxy@hecs-165234 mklib]$ gcc -c mymath.c -o mymath.o -std=c99
- [lxy@hecs-165234 mklib]$ gcc -c myprint.c -o myprint.o -std=c99
- [lxy@hecs-165234 mklib]$ ll
- total 24
- -rw-rw-r-- 1 lxy lxy 141 Oct 12 12:53 mymath.c
- -rw-rw-r-- 1 lxy lxy 53 Oct 12 12:43 mymath.h
- -rw-rw-r-- 1 lxy lxy 1264 Oct 12 12:54 mymath.o
- -rw-rw-r-- 1 lxy lxy 95 Oct 12 12:54 myprint.c
- -rw-rw-r-- 1 lxy lxy 73 Oct 12 12:43 myprint.h
- -rw-rw-r-- 1 lxy lxy 1584 Oct 12 12:54 myprint.o
下面,我们写一个自己的源文件,调用这里的函数,我们现在只把.h 和 .o 给别人,那么别人可以使用吗?
- // main.c
- #include"mymath.h"
- #include"myprint.h"
-
-
- int main()
- {
- int res = sum(10,20);
- printf("sum: %d\n", res);
-
- Print("hello world");
- return 0;
- }
- [lxy@hecs-165234 uselib]$ ll
- total 20
- -rw-rw-r-- 1 lxy lxy 145 Oct 12 13:00 main.c
- -rw-rw-r-- 1 lxy lxy 53 Oct 12 13:02 mymath.h
- -rw-rw-r-- 1 lxy lxy 1264 Oct 12 13:00 mymath.o
- -rw-rw-r-- 1 lxy lxy 73 Oct 12 13:02 myprint.h
- -rw-rw-r-- 1 lxy lxy 1584 Oct 12 13:00 myprint.o
下面我们将 main.c 编译为 .o 文件,然后和 其他的 .o 文件编译到一起,可以使用吗?
- [lxy@hecs-165234 uselib]$ gcc -c main.c -o main.o
- [lxy@hecs-165234 uselib]$ gcc -o my.exe main.o mymath.o myprint.o -std=c99
- [lxy@hecs-165234 uselib]$ ll
- total 36
- -rw-rw-r-- 1 lxy lxy 145 Oct 12 13:00 main.c
- -rw-rw-r-- 1 lxy lxy 1680 Oct 12 13:03 main.o
- -rwxrwxr-x 1 lxy lxy 8536 Oct 12 13:03 my.exe
- -rw-rw-r-- 1 lxy lxy 53 Oct 12 13:02 mymath.h
- -rw-rw-r-- 1 lxy lxy 1264 Oct 12 13:00 mymath.o
- -rw-rw-r-- 1 lxy lxy 73 Oct 12 13:02 myprint.h
- -rw-rw-r-- 1 lxy lxy 1584 Oct 12 13:00 myprint.o
下面我们运行一下 my.exe 文件,试一下可不可行。
- [lxy@hecs-165234 uselib]$ ./my.exe
- sum: 165
- hello world: [1697087058]
是可以的,所以这样子是一种使用别人库的方法。
但是如果我们这样使用的话,如果有很多 .o 文件,那么是很容易丢弃的,所以其实这样并不好使用。
那么我们实际上可以将所有的 .o 文件都打包,而这个打包也就叫形成库文件。
ar -rc lib + 库名 + .a file.o ...
这里的 ar 就是命令 表示的就是 archive(存档/归档)
这里的 r 表示 replace
这里的 c 表示 create
库的名字是 lib 然后加上库名 再加上 .a 而这个名字是随意的。
后面再加上想要归档的 .o 文件。
- [lxy@hecs-165234 uselib]$ ar -rc libhello.a mymath.o myprint.o
- [lxy@hecs-165234 uselib]$ ll
- total 40
- -rw-rw-r-- 1 lxy lxy 3058 Oct 12 13:12 libhello.a
- -rw-rw-r-- 1 lxy lxy 145 Oct 12 13:00 main.c
- -rw-rw-r-- 1 lxy lxy 1680 Oct 12 13:03 main.o
- -rwxrwxr-x 1 lxy lxy 8536 Oct 12 13:03 my.exe
- -rw-rw-r-- 1 lxy lxy 53 Oct 12 13:02 mymath.h
- -rw-rw-r-- 1 lxy lxy 1264 Oct 12 13:00 mymath.o
- -rw-rw-r-- 1 lxy lxy 73 Oct 12 13:02 myprint.h
- -rw-rw-r-- 1 lxy lxy 1584 Oct 12 13:00 myprint.o
下面,我们可以将该过程写成自动化的 makefile
- libhello.a:mymath.o myprint.o
- ar -rc libhello.a mymath.o myprint.o
- mymath.o:mymath.c
- gcc -c mymath.c -o mymath.o -std=c99
- myprint.o:myprint.c
- gcc -c myprint.c -o myprint.o
- .PHONY:clean
- clean:
- rm -rf libhello.a mymath.o myprint.o
首先我们想要形成库文件,这里的库文件是 libhello.a 文件,而依赖方法就是 ar 归档。
该文件依赖于两个 .o 文件。
但是 .o 文件又依赖于 .c 文件。
这里的 mymath.o 依赖于 mymath.c,myprint.o 依赖于 myprint.c,而依赖方法就是 gcc -c 形成 .o 文件。
下面既然库有了,那么就可以考虑如何发布了。
一般情况下,我们发布一个库,我们的一个文件里面,有一个 include 文件,该文件里面包含的就是库的头文件,还有一个 lib 文件,该文件里面就是库文件。
所以下面我们就可以创建一个 hello 的库,该目录里面有一个 include 和 lib 目录,这两个目录中一个是 .h 文件,一个是 .a 文件。
- // 继续使用自动化工具
- libhello.a:mymath.o myprint.o
- ar -rc libhello.a mymath.o myprint.o
- mymath.o:mymath.c
- gcc -c mymath.c -o mymath.o -std=c99
- myprint.o:myprint.c
- gcc -c myprint.c -o myprint.o
- .PHONY:hello
- hello:
- mkdir -p hello/include
- mkdir -p hello/lib
- cp *.h hello/include
- cp *.a hello/lib
-
- .PHONY:clean
- clean:
- rm -rf libhello.a mymath.o myprint.o hello
下面我们可以使用一下这个库。
我们将该库拷贝到,想要使用这个库的目录下。
- [lxy@hecs-165234 uselib]$ ll
- total 8
- drwxrwxr-x 4 lxy lxy 4096 Oct 12 22:24 hello
- -rw-rw-r-- 1 lxy lxy 145 Oct 12 13:00 main.c
下面开始我们会说三种使用方法:
第一种
将 hello 的 include 的内容拷贝到系统的 /usr/include 目录下
将 hello 的 lib 的内容拷贝到系统的 /lib64 or /usr/lib64 目录下
- [lxy@hecs-165234 uselib]$ sudo cp hello/include/* /usr/include/
- [sudo] password for lxy:
- [lxy@hecs-165234 uselib]$ ls /usr/include/myprint.h
- /usr/include/myprint.h
- [lxy@hecs-165234 uselib]$ ls /usr/include/mymath.h
- /usr/include/mymath.h
- [lxy@hecs-165234 uselib]$ sudo cp hello/lib/* /lib64
- [lxy@hecs-165234 uselib]$ ls /lib64/libhello.a
- /lib64/libhello.a
下面我们编译一下:
- [lxy@hecs-165234 uselib]$ gcc -o my.exe main.c
- /tmp/ccps443p.o: In function `main':
- main.c:(.text+0x13): undefined reference to `sum'
- main.c:(.text+0x34): undefined reference to `Print'
- collect2: error: ld returned 1 exit status
为什么报错了?
实际上,我们自己写的库,或者别人给的库,那么都是属于第三方库的,所以gcc/g++并不认识我们的库,而他们自己的库,gcc/g++是认识的,所以我们需要指明库才可以:
下面指明库,我们需要带 -l 选项,后面就是我们库的名字,但是库名是去掉 lib 再去掉 .a 就是库名:
- [lxy@hecs-165234 uselib]$ gcc main.c -lhello
- [lxy@hecs-165234 uselib]$ ll
- total 20
- -rwxrwxr-x 1 lxy lxy 8536 Oct 12 22:39 a.out
- drwxrwxr-x 4 lxy lxy 4096 Oct 12 22:24 hello
- -rw-rw-r-- 1 lxy lxy 145 Oct 12 22:34 main.c
- [lxy@hecs-165234 uselib]$ ./a.out
- sum: 165
- hello world [1697121602]
我们上面所做的,将头文件于库文件拷贝到系统目录下,就是所谓的“安装”。
第二种
就是直接使用:
我们直接编译,但是我们的源文件中包含的库的头文件和函数的定义如果不在我们的源文件的当前目录下。
那么我就需要指明头文件的目录,和库文件的目录。
但是光指明库文件的目录,还是不可以编译,因为我们不知道我们要链接哪一个库,所以我们还是需要指明库。
gcc 文件名 -I + 头文件路径 -L + 库文件路径 -l + 库文件名 -o + 想要形成的可执行程序名
结果:
- [lxy@hecs-165234 uselib]$ gcc main.c -I hello/include -L hello/lib -lhello
- [lxy@hecs-165234 uselib]$ ./a.out
- sum: 165
- hello world [1697122308]
上面就编译好了,也可以运行出来。
但是第三种吗,这里没有必要再静态库里面使用,到下面动态库里面我们再使用。
动态库和静态库的后缀名有一点区别,动态库是 .so 而静态库是 .a.
下面,我们先使用指令制作动态库,后面再使用自动化工具。
gcc -fPIC -c file1.c -o file2.c
-fPIC:表示这次要生成的是动态库
- [lxy@hecs-165234 mklib]$ gcc -fPIC -c mymath.c -o mymath.o -std=c99
- [lxy@hecs-165234 mklib]$ gcc -fPIC -c myprint.c -o myprint.o -std=c99
这一次生成 .o 文件后,不适用 ar 打包,而是还是使用 gcc 命令
gcc -shared file1.o file2.o -o libfile.so
-shared:表示这次要形成共享库
下面我们使用自动化工具编写:
- .PHONY:all
- all:libhello.so libhello.a
-
- libhello.so:mymath_d.o myprint_d.o
- gcc -shared mymath_d.o myprint_d.o -o libhello.so
- mymath_d.o:mymath.c
- gcc -c -fPIC mymath.c -o mymath_d.o -std=c99
- myprint_d.o:myprint.c
- gcc -c -fPIC myprint.c -o myprint_d.o -std=c99
-
- libhello.a:mymath.o myprint.o
- ar -rc libhello.a mymath.o myprint.o
- mymath.o:mymath.c
- gcc -c mymath.c -o mymath.o -std=c99
- myprint.o:myprint.c
- gcc -c myprint.c -o myprint.o
- .PHONY:output
- output:
- mkdir -p output/include
- mkdir -p output/lib
- cp *.h output/include
- cp *.a *.so output/lib
-
- .PHONY:clean
- clean:
- rm -rf *.a *.so *.o output
这次,我们使用 makefile 依次形成两个库
首先,我们有一个伪目标,all,而 all 依赖于两个库,所以就需要形成连个库
所以这样就可以一次性形成多个文件了
想要形成动态库,那么就依赖于两个 .o 文件,但是这里为了和下面的 .o 文件不重复,所以需要换一个名字。
要想形成两个 .o 文件,那么又依赖于 . c 文件,然后我们就执行格子的依赖方法即可。
下面我们将 hello 换成 output 表示发布的意思。
然后将.h 文件拷贝到 ./output.include 目录下
将库文件拷贝到 ./output/lib 目录下。
这样就是发布了,然后删除的时候,将所有的 .a .so .o 和 output 都删掉。
下面制作好动态库后,我们看一下如何使用动态库。
我们当然也可以将头文件和库文件分别拷贝到系统目录下。
但是下面我们不这样做,我们还有其他的做法。
首先我们还是将 output 目录拷贝到想要使用该库的目录下,这样看起来方便一点,其实不拷贝也可以。
然后我们像静态库的第二种使用方法一样,直接编译。
[lxy@hecs-165234 uselib]$ gcc main.c -I./output/include -L ./output/lib -l hello
-I(大 i):指明头文件路径
-L:指明库文件路径
-l(小 L):指明哪一个库(去掉前缀去掉后缀)
这样就编译好了,但是我们难道没有一个疑问吗?
我们的 ./output/lib 目录下可是有两个 hello 的库的,那么他是选择哪一个呢?
我们可以使用 ldd 看一下:
- [lxy@hecs-165234 uselib]$ ldd a.out
- linux-vdso.so.1 => (0x00007fff55f8c000)
- libhello.so => not found
- libc.so.6 => /lib64/libc.so.6 (0x00007fe1efc3c000)
- /lib64/ld-linux-x86-64.so.2 (0x00007fe1f000a000)
这里看到 a.out 使用了 .so ,也就是说明如果再默认情况下,那么会优先使用动态库。
现在我们运行一下看行不行!
- [lxy@hecs-165234 uselib]$ ./a.out
- ./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
这里运行失败了,我们看一下报错信息。
他显示说,加载共享库的失败,没法打开共享库。
这是为什么呢?
下面我们就说一下动态库的加载原理:
再一个程序中又动态库的话,那么动态库实际上是可以后面加载的,也就是可以先将代码和数据加载进去。
但是等需要库的时候,再把库里面的代码和数据加载进去。
而一般的代码和数据就加载到一个地址空间的代码段了。
但是动态库一般就加载到“共享区”了(堆和栈中间的一段镂空的位置)。
但是我们再编译的时候,我们以及告诉编译器库在哪了,但是再加载的时候找不到。
因为加载的时候就和编译器没有关系了,就和系统中的加载器有关系了,但是加载器又不知道你的库再哪,所以就报错了。
那么要怎么办呢?
下面又三种解决方案:
第一种
就是将库文件拷贝到系统的 /lib64/ 目录下,那么就可以找到了。
第二种
再系统的 /lib64/ 目录下建立软连接,链接到对应的库的位置
[lxy@hecs-165234 uselib]$ sudo ln -s /home/lxy/lesson/linux107/uselib/output/lib/libhello.so /lib64/libhello.so
结果:
- [lxy@hecs-165234 uselib]$ ls /lib64 | grep hello
- libhello.so
这样就建立好了,那么 a.out 程序也就可以开始运行了。
- [lxy@hecs-165234 uselib]$ ./a.out
- sum: 165
- hello world [1697164076]
第三种
如果不想将程序拷贝到系统目录下,或者也不想建立软连接的话,那么其实可以配置环境变量
LD_LIBRARY_PATH
这个环境变量就是每次库文件的搜索路径,所以我们可以将库文件所在的路径添加到该环境变量中:
[lxy@hecs-165234 uselib]$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/lxy/lesson/linux107/uselib/output//lib/
添加到该环境变量中
而添加方法就是中间以 : 分割
下面看一下是否可以执行 a.out
- [lxy@hecs-165234 uselib]$ ./a.out
- sum: 165
- hello world [1697164557]
第四种
由于上面是添加的是该会话的环境变量,如果我们想要永久添加,那么我们可以直接配置到系统中。
而系统的配置再 /etc/ld.so.conf.d/ 路径下。
但是由于是系统的,所以配置之后需要刷新一下 ldconfig
- //进入到 /etc/ld.so.conf.d/ 目录下
- [lxy@hecs-165234 uselib]$ cd /etc/ld.so.conf.d/
- [lxy@hecs-165234 ld.so.conf.d]$ ll
- total 24
- -rw-r--r-- 1 root root 26 Jul 19 20:48 bind-export-x86_64.conf
- -r--r--r-- 1 root root 63 Jan 14 2022 kernel-3.10.0-1160.53.1.el7.x86_64.conf
- -r--r--r-- 1 root root 63 May 4 23:25 kernel-3.10.0-1160.90.1.el7.x86_64.conf
- -r--r--r-- 1 root root 63 Jul 24 22:04 kernel-3.10.0-1160.95.1.el7.x86_64.conf
- -r--r--r--. 1 root root 63 Nov 9 2018 kernel-3.10.0-957.el7.x86_64.conf
- -rw-r--r-- 1 root root 17 Jun 21 21:31 mysql-x86_64.conf
实际上这里的配置其实就是很简单,只需要创建一个文件,然后将对应的库路径写进去就好了。
但是该文件必须以 .conf 结尾。
- [lxy@hecs-165234 ld.so.conf.d]$ cat mylibrary.conf
- /home/lxy/lesson/linux107/uselib/output/lib/
但是这里的创建文件,或者是写入文件都需要 root 权限
等配置结束后,ldconfig 刷新一下就可以了。
- [lxy@hecs-165234 ld.so.conf.d]$ /home/lxy/lesson/linux107/uselib/a.out
- sum: 165
- hello world [1697165129]
这样就运行出来了。
上面就是库的制作于使用的全部内容了~