自定义实现一个静态库
源文件的书写
创建以下四个文件,分别是Add
与Sub
函数的声明头文件以及它们的定义源文件
my_add.h
#pragma once
#include <stdio.h>
extern int Add(int a, int b);
my_sub.h
#pragma once
#include <stdio.h>
extern int Sub(int a, int b);
my_add.c
#include "my_add.h"
int Add(int a, int b)
{
printf("enter Add func:%d + %d\n",a,b);
return a+b;
}
my_sub.c
#include "my_sub.h"
int Sub(int a, int b)
{
printf("enter Sub func:%d - %d\n",a,b);
return a-b;
}
按照我们之前的使用方法是:
创建一个main.c
文件去调用它们,比如:
#include "my_add.h"
#include "my_sub.h"
int main()
{
int a=10;
int b=20;
int tmp=Add(a,b);
printf("result:%d\n",tmp);
tmp=Sub(a,b);
printf("result:%d\n",tmp);
return 0;
}
1.生成可执行程序:
[AMY@VM-12-15-centos lesson_13]$ gcc -o myexe main.c my_add.c my_sub.c
运行结果:
2.我们还有一种方法,先使用gcc -c
(汇编),使其变成可重定位目标二进制文件,后缀是.o
[AMY@VM-12-15-centos lesson_13]$ gcc -c main.c my_add.c my_sub.c
[AMY@VM-12-15-centos lesson_13]$ ll
total 32
-rw-rw-r-- 1 AMY AMY 201 Jun 25 20:53 main.c
-rw-rw-r-- 1 AMY AMY 1736 Jun 25 21:02 main.o
-rw-rw-r-- 1 AMY AMY 110 Jun 25 20:55 my_add.c
-rw-rw-r-- 1 AMY AMY 64 Jun 25 20:47 my_add.h
-rw-rw-r-- 1 AMY AMY 1536 Jun 25 21:02 my_add.o
-rw-rw-r-- 1 AMY AMY 110 Jun 25 20:55 my_sub.c
-rw-rw-r-- 1 AMY AMY 64 Jun 25 20:49 my_sub.h
-rw-rw-r-- 1 AMY AMY 1544 Jun 25 21:02 my_sub.o
然后我们将这些.o
文件链接起来,其实跟上面第一种也是同样的道理
[AMY@VM-12-15-centos lesson_13]$ gcc -o myexe main.o my_add.o my_sub.o
这样也同样可以生成可执行文件myexe
,到目前为止你可能觉得多次一举,请你接下来慢慢看!
关于库的知识引入
现在左边的人(目录)需要使用右边的人(目录)提供的库中的代码,但是右边的人,不想要将它的源文件给左边的人,反正.o
文件下一步就是链接,所以它将这些.c
源文件先预处理编译汇编后的.o
文件给左边的人
但是我们现在对main.c处理,会出现下面报错:
[AMY@VM-12-15-centos test]$ gcc -c main.c
main.c:1:20: fatal error: my_add.h: No such file or directory
#include "my_add.h"
^
compilation terminated.
原因是:编译源文件第一步就是预处理将头文件展开,main.c使用了原来库的方法,但是它没有头文件所以预处理就失败,所以我们还需要为库的使用者提供.h
头文件
[AMY@VM-12-15-centos lesson_13]$ cp *.h ../test
这样我们的使用者就可以执行代码了:
[AMY@VM-12-15-centos test]$ gcc -c main.c
[AMY@VM-12-15-centos test]$ gcc -o myexe main.o my_add.o my_sub.o
[AMY@VM-12-15-centos test]$ ./myexe
enter Add func:10 + 20 =
result:30
enter Sub func:10 - 20 =
result:-10
如果我们不想给对方提供我们的源代码,我们可以提供.o
可重定位二进制文件,让使用者直接用它的代码链接即可。
我们可以给对方提供
.o
(方法的实现) +.h
(有什么方法)。基于这样我们可以试着将所有的.o
文件打一个包,给使用者提供一个库文件即可。
本质上说,打包就是将多个.o
,形成一个文件,将所有的方法都放在这个文件里,这个文件就叫做库,实际过程中根据打包方式的不同就有了动态库与静态库。
库的本质就是.o
文件的集合
静态库打包
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
Linux上的库文件:库的命名规则:libxxx
(库名) + .
类型后缀.a/.so
,静态库后缀.a
,动态库后缀.so
生成静态库
ar -rc libmymath.a add.o sub.o
ar(archive)是gnu归档工具,rc表示(replace and create)
[AMY@VM-12-15-centos lesson_13]$ file libmymath.a
libmymath.a: current ar archive(当前ar存档)
我们创建Makefile
文件用来编译:
libmymath.a:my_add.o my_sub.o
ar -rc $@ $^
my_add.o:my_add.c
gcc -c my_add.c -o my_add.o
my_sub.o:my_sub.c
gcc -c my_sub.c -o my_sub.o
.PHONY:clean
clean:
rm -rf *.o libmymath.a
libmymath.a
就是我们形成的库文件,但是我们现在就能直接使用吗?答案是不能!!
我们之所以能够在Linux之中写C语言代码,就是因为Linux系统提供了我们编写代码所需的头文件和库文件:
[AMY@VM-12-15-centos lesson_13]$ ls /usr/include/stdio.h
/usr/include/stdio.h
[AMY@VM-12-15-centos lesson_13]$ ls /lib64/libc.a
/lib64/libc.a
将一个库交付给使用者,就是要将库文件.a/.so
+ 匹配的头文件一起交付
所以我们还需要增加一点命令到Makefile
中:
libmymath.a:my_add.o my_sub.o
ar -rc $@ $^
my_add.o:my_add.c
gcc -c my_add.c -o my_add.o
my_sub.o:my_sub.c
gcc -c my_sub.c -o my_sub.o
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp -f *.a mylib/lib
cp -f *.h mylib/include
.PHONY:clean
clean:
rm -rf *.o libmymath.a mylib
测试流程:
[AMY@VM-12-15-centos lesson_13]$ make
gcc -c my_add.c -o my_add.o
gcc -c my_sub.c -o my_sub.o
ar -rc libmymath.a my_add.o my_sub.o
[AMY@VM-12-15-centos lesson_13]$ ls
libmymath.a Makefile my_add.c my_add.h my_add.o my_sub.c my_sub.h my_sub.o
[AMY@VM-12-15-centos lesson_13]$ make output
mkdir -p mylib/include
mkdir -p mylib/lib
cp -f *.a mylib/lib
cp -f *.h mylib/include
[AMY@VM-12-15-centos lesson_13]$ ls
libmymath.a Makefile my_add.c my_add.h my_add.o mylib my_sub.c my_sub.h my_sub.o
[AMY@VM-12-15-centos lesson_13]$ tree mylib
mylib
├── include
│ ├── my_add.h
│ └── my_sub.h
└── lib
└── libmymath.a
2 directories, 3 files
到了这里,我们就相当于把我们的库发布出来,如果我们想让被人使用还需要打包(成为压缩文件):
[AMY@VM-12-15-centos lesson_13]$ tar czf mylib.tgz mylib
使用者将包文件解压:
[AMY@VM-12-15-centos test]$ tar xzf mylib.tgz
[AMY@VM-12-15-centos test]$ ll
total 12
-rw-rw-r-- 1 AMY AMY 201 Jun 25 21:18 main.c
drwxrwxr-x 4 AMY AMY 4096 Jun 25 22:19 mylib
-rw-rw-r-- 1 AMY AMY 965 Jun 25 22:23 mylib.tgz
[AMY@VM-12-15-centos test]$ tree mylib
mylib
├── include
│ ├── my_add.h
│ └── my_sub.h
└── lib
└── libmymath.a
2 directories, 3 files
安装的本质就是拷贝,就是将这些头文件和库文件拷贝到系统指定存放(头文件库文件)且能找到的地方
作为使用者如何使用静态库
让我们一步一步,发现错误并解决错误
当前目录文件:
[AMY@VM-12-15-centos test]$ ll
total 8
-rw-rw-r-- 1 AMY AMY 201 Jun 25 21:18 main.c
drwxrwxr-x 4 AMY AMY 4096 Jun 25 22:19 mylib
我们直接开始使用gcc
编译:
[AMY@VM-12-15-centos test]$ gcc -o mymath main.c
main.c:1:20: fatal error: my_add.h: No such file or directory
#include "my_add.h"
^
compilation terminated.
gcc是编译器,编译器在搜索文件时,先搜索当前路径,再去系统默认指定路径去搜索
错误:找不到头文件也找不到库文件,虽然说这些头文件以及库文件都在mylib
路径下,但是跟main.c
并不是处于同级目录下,编译器无法找到,并且再系统指定路径也没有找到,所以这里出现错误
为了解决上述编译器搜索不到的问题,我们可以给gcc
指定搜索路径:
[AMY@VM-12-15-centos test]$ gcc -o mymath main.c -I ./mylib/include
/tmp/cciXM8Xc.o: In function `main':
main.c:(.text+0x21): undefined reference to `Add'
main.c:(.text+0x47): undefined reference to `Sub'
collect2: error: ld returned 1 exit status
错误:我们已经指定搜索路径了,这里报错跟上面已经不一样了,现在的错误是很明显是链接错误,到了链接错误,说明我们已经完成了预处理编译汇编的任务,离成功已经不远了。现在的问题就是头文件已经找到了,但是库文件还找不到
我们同样可以指定库文件搜索路径:(我们可以理解-I
就是include
首字母大写、-L
就是lib
首字母大写)
[AMY@VM-12-15-centos test]$ gcc -o mymath main.c -I ./mylib/include -L ./mylib/lib
/tmp/ccHHUV6L.o: In function `main':
main.c:(.text+0x21): undefined reference to `Add'
main.c:(.text+0x47): undefined reference to `Sub'
collect2: error: ld returned 1 exit status
错误:虽然我们指定了库文件的搜索路径,但是还是报了编译错误,这又是为什么呢??
原因:如果要链接第三方的库,我们必须要指明库名称,也就是如果我们要链接mylib
下lib
中的libmymath.a
库文件,我们必须要写明这个库的名称,也就是mymath
(库的名称我们需要去掉前缀lib
去掉后缀.a/.so
)
为什么我们使用别的语言比如C、C++不需要指定库名称呢?因为它们默认就指定了使用什么库,gcc、g++也会默认识别自带的库。而我们使用第三方库,编译器不知道你到底要执行什么库,而且当前目录也可能存在多个库文件,有太多不确定因素
最终的正确编译指令:-l
是指定库名
gcc -o mymath main.c -I ./mylib/include -L ./mylib/lib -l mymath
关于生成静态库相关问题
截至上一步我们已经将静态库生成好,并且使用者使用这个静态库且生成了可执行程序,我们现在使用ldd
与file
命令来查看这个可执行程序的基本信息:
[AMY@VM-12-15-centos test]$ ldd mymath
linux-vdso.so.1 => (0x00007ffed21b1000)
libc.so.6 => /lib64/libc.so.6 (0x00007fda8fa78000)
/lib64/ld-linux-x86-64.so.2 (0x00007fda8fe46000)
[AMY@VM-12-15-centos test]$ file mymath
mymath: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32,
BuildID[sha1]=fdb301e54e2ada49fe9c3ea53d59a8632188c59e, not stripped
问题:我们使用ldd
并没有发现我们自己写的库,而且我们发现我们生成的静态库生成的可执行程序链接的却都是.so
后缀的动态库,我们使用file
命令也发现mymath
是dynamically linked
动态链接
gcc默认是动态链接,对于一个特定的库,到底是动态链接还是静态链接取决于你提供的是动态库还是静态库,但如果动静态库都提供给你了,默认是链接动态库。而且,一个可执行程序,可能不止链接一个库,只要链接了动态库,那就是动态链接。而在这里虽然我们只提供了静态库,但是gcc没有办法,只能将这个库拷贝来使用。
另外一种使用静态库的方法(安装库到本地)
下面的这种行为就是安装:
[AMY@VM-12-15-centos test]$ tree mylib
mylib
├── include
│ ├── my_add.h
│ └── my_sub.h
└── lib
└── libmymath.a
2 directories, 3 files
[AMY@VM-12-15-centos test]$ sudo cp mylib/include/* /usr/include/
[AMY@VM-12-15-centos test]$ sudo cp mylib/lib/*.a /lib64/
当然,我们还是需要指定库名称,因为这是我们自己写的第三方库,而这个目录下的库文件太多,gcc不能自己识别:
[AMY@VM-12-15-centos test]$ gcc main.c -l mymath
运行结果:
没有经过测试发布的库,不建议直接拷贝到系统库目录下
卸载操作:
[AMY@VM-12-15-centos test]$ sudo rm /usr/include/my_*
[AMY@VM-12-15-centos test]$ sudo rm /lib64/libmymath.a
如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀