今天在交流群里面看到有一个网友问了一个内联函数的问题,原本想写这个文章的;由于已经提前说写静态链接库的制作和使用,所以内联函数的文章,明天来写!在开始写这个文章之前,先会讲函数库,然后再讲解静态链接库:
函数库
1.什么是函数库:
(1)函数库就是一些事先写好的函数的集合,给别人复用。
(2)函数是模块化的,因此可以被复用。我们写好了一个函数,可以被反复使用。也可以是你写好了一个函数然后共享出来,当别人有相同的需求时就不需自己写直接用你写好的这个函数即可(类似我们经常写的stm32的程序,就会遇到这种情况,经常要自己写函数,然后当写其他外设的时候,要用到上次写好外设的函数的时候,就可以直接调用,就不用重新去写了。)。
2.函数库的由来:
(1)最开始是没有函数库,每个人写程序都要从零开始自己写。时间长了慢慢的早期的程序员就积累下来了一些有用的函数。
(2)早期的程序员经常参加行业聚会,在聚会上大家互相交换各自的函数库。
(3)后来程序员中的一些牛逼的大神就提出把大家各自的函数库收拢在一起,然后经过校准和整理,最后形成了一份标准化的函数库,就是现在的标准的函数库,譬如说glibc。
3.函数库的提供形式:
(1)早期的函数共享都是以源代码的形式进行的。这种方式共享是最彻底的(后来这种源码共享的方向就形成了我们现在的开源社区)。但是这种方式有它的缺点,缺点就是无法以商业化形式来发布函数库。
(2)商业公司需要将自己的有用的函数库共享给被人(当然是付费的),但是又不能给客户源代码(这个确实是这样,如果你上班一不小心源码给客户,那就损失比较大了,而且一般公司里面的源码也是不买出去的)。这时候的解决方案就是以库(主要有2种:静态库和动态库)的形式来提供。
(3)比较早出现的是静态链接库。静态库其实就是商业公司将自己的函数库源代码经过只编译不连接形成.o的目标文件,然后用ar工具(这个工具暂时自身没有使用过)将.o文件归档成.a的归档文件(.a的归档文件又叫静态链接库文件)。商业公司通过发布.a库文件和.h头文件来提供静态库给客户使用;客户拿到.a和.h文件后,通过.h头文件得知库中的库函数的原型,然后在自己的.c文件中直接调用这些库文件,在连接的时候链接器会去.a文件中拿出被调用的那个函数的编译后的.o二进制代码段链接进去形成最终的可执行程序。
(4)动态链接库比静态链接库出现的晚一些,效率更高一些,是改进型的。现在我们一般都是使用动态库。静态库在用户链接自己的可执行程序时就已经把调用的库中的函数的代码段链接进最终可执行程序中了,这样好处是可以执行,坏处是太占地方了。尤其是有多个应用程序都使用了这个库函数时,实际上在多个应用程序最后生成的可执行程序中都各自有一份这个库函数的代码段。当这些应用程序同时在内存中运行时,实际上在内存中有多个这个库函数的代码段,这完全重复了。而动态链接库本身不将库函数的代码段链接入可执行程序,只是做个标记。然后当应用程序在内存中执行时,运行时环境发现它调用了一个动态库中的库函数时,会去加载这个动态库到内存中,然后以后不管有多少个应用程序去调用这个库中的函数都会跳转到第一次加载的地方去执行(不会重复加载)。下面举个简单例子的来分析:
代码语言:javascript复制#include <stdio.h>
int main()
{
printf("hellon");
return 0;
}
接着我们来看一下这个程序编译后占多大内存:
代码语言:javascript复制root@ubuntu-virtual-machine:/home/ubuntu# ls -l
总用量 70756
-rw-r--r-- 1 root root 173 1月 23 02:04 1
-rwxr-xr-x 1 root root 8304 1月 28 21:56 a.out
我们可以看到gcc编译生成的a.out占用8304个字节大小。我们使用静态链接库看看是什么
情况(gcc中编译链接程序默认是使用动态库的):
代码语言:javascript复制root@ubuntu-virtual-machine:/home/ubuntu# gcc hello.c -static
root@ubuntu-virtual-machine:/home/ubuntu# ls -l
总用量 71576
-rw-r--r-- 1 root root 173 1月 23 02:04 1
-rw-r--r-- 1 root root 75 1月 28 21:58 '1'
-rwxr-xr-x 1 root root 844704 1月 28 22:02 a.out
我们可以看到使用静态链接库后,它的内存大小就变成了844704个字节了。从中我们可以发现使用静态链接库生成的可执行的程序比较占用内存大小。
4.库函数的使用:
(1)gcc中编译链接程序默认是使用动态库的,要想静态链接需要显式用-static来强制静态链接。
(2)库函数的使用需要注意4点:
第一:包含相应的头文件。
第二:调用库函数时注意函数原型。
第三:有些库函数链接时需要额外用-lxxx来指定链接(这个之前在讲多线程的文章里面在编译的时候,就要加-lpthread,不然直接编译程序会报错的)。
第四:如果是动态库,要注意-L指定动态库的地址(后面会讲,这里先了解一下)。
静态链接库的制作和使用
1.自己制作静态链接库:
这里我先在当前目录创建两个文件一个是hell.c ,另外一个是hell.h,然后在hell.c里面写上;
代码语言:javascript复制 #include <stdio.h>
void fun1(void)
{
printf("hellon");
}
然后把这个函数的原型写到hell.h文件当中去:
代码语言:javascript复制 void fun1(void);
然后同样也是在当前目录下写一个Makefile文件来记录生成静态链接库的过程,这个文件里面写:
代码语言:javascript复制all:
gcc hell.c -o hell.o -c
ar -rc libhell.a hell.o
说明:
首先使用gcc -c只编译不连接,生成.o文件;然后使用ar工具进行打包成.a归档文件。
库名不能随便乱起,一般是lib 库名称,后缀名是.a表示是一个归档文件。
注意:制作出来了静态库之后,发布时需要发布.a文件和.h文件。关于这个个什么是Makefile文件,可以看这个(同时还要注意这里直接生成静态链接库的时候,使用make命令,这个要使用apt install make,不然直接使用会报错的):https://blog.csdn.net/haoel/article/details/2886;下面是详细过程:
代码语言:javascript复制root@ubuntu-virtual-machine:/home/ubuntu/rest# ls
hell.c hell.h
root@ubuntu-virtual-machine:/home/ubuntu/rest# vim hell.c
root@ubuntu-virtual-machine:/home/ubuntu/rest# vim hell.h
root@ubuntu-virtual-machine:/home/ubuntu/rest# touch Makefile
root@ubuntu-virtual-machine:/home/ubuntu/rest# vim Makefile
root@ubuntu-virtual-machine:/home/ubuntu/rest# make
gcc hell.c -o hell.o -c
hell.c: In function ‘fun1’:
hell.c:4:6: warning: implicit declaration of function
‘printf’ [-Wimplicit-function-declaration]
printf("hellon");
^~~~~~
hell.c:4:6: warning: incompatible implicit declaration of
built-in function ‘printf’
hell.c:4:6: note: include ‘<stdio.h>’ or provide a
declaration of ‘printf’
ar -rc libhell.a hell.o
root@ubuntu-virtual-machine:/home/ubuntu/rest# vim hell.c
root@ubuntu-virtual-machine:/home/ubuntu/rest# make
gcc hell.c -o hell.o -c
ar -rc libhell.a hell.o
root@ubuntu-virtual-machine:/home/ubuntu/rest# ls
hell.c hell.h hell.o libhell.a Makefile
2.使用静态链接库:
上面创建好了静态链接库,现在我们就来使用这个静态链接库,然后我在当前目录下再创建一个目录叫做testlib,然后把hell.h和libhell.a移到这个目录下面同时在这个目录下面创建一个test.c文件:
代码语言:javascript复制root@ubuntu-virtual-machine:/home/ubuntu/rest# mkdir testlib
root@ubuntu-virtual-machine:/home/ubuntu/rest# mv hell.h testlib
root@ubuntu-virtual-machine:/home/ubuntu/rest# mv libhell.a testlib
root@ubuntu-virtual-machine:/home/ubuntu/rest# cd testliib/
bash: cd: testliib/: 没有那个文件或目录
root@ubuntu-virtual-machine:/home/ubuntu/rest# cd testlib/
root@ubuntu-virtual-machine:/home/ubuntu/rest/testlib# ls
hell.h libhell.a
root@ubuntu-virtual-machine:/home/ubuntu/rest/testlib# touch test.c
然后的话,我在这个test.c文件里面就使用刚才那个创建的函数fun1();
代码语言:javascript复制#include <stdio.h>
#include "hell.h"
int main(void)
{
fun1();
return 0;
}
现在的话,我们来看效果,这个时候直接编译的话,会显示找不到这个函数:
代码语言:javascript复制root@ubuntu-virtual-machine:/home/ubuntu/rest/testlib# gcc test.c -o test
/tmp/ccmAbHWC.o:在函数‘main’中:
test.c:(.text 0x5):对‘fun1’未定义的引用
collect2: error: ld returned 1 exit status
说明我们还没有用到我们的静态链接库(这个时候就可以看到我上面写的那个函数库注意的地方的第三点,使用-lhell来链接到这个库):
代码语言:javascript复制root@ubuntu-virtual-machine:/home/ubuntu/rest/testlib# gcc test.c -o test -lhell
/usr/bin/ld: 找不到 -lhell
collect2: error: ld returned 1 exit status
上面又会报错,说找不到我们这个静态链接库(所以我们又可以使用函数库注意里面的第四点,使用"-L."把静态链接库指定到当前目录下面),就能编译通过了:
代码语言:javascript复制root@ubuntu-virtual-machine:/home/ubuntu/rest/testlib# gcc test.c -o test -lhell -L.
root@ubuntu-virtual-machine:/home/ubuntu/rest/testlib# ./test
hello
总结
今天的静态链接库的制作和使用,就分享到这里了,文章里面有讲到关于Makefile,这里可以参考我发的那个链接,里面讲的非常详细。明天继续分享动态链接库的制作和使用以及内联函数的使用和概念。