目录
在之前我们学习程序编译的步骤时,我们知道程序编译的步骤为预处理,编译,汇编,链接。在源文件经过汇编之后原来的.c文件成了.o文件,生成的.o文件通过链接动静态库形成了可执行程序,那么动静态库到底是什么呢?有什么联系呢?本期将为大家详细的讲解动静态库。
动静态库概念
在linux中,静态库通常以.a作为后缀,动态库以.so作为后缀。

阅读如下代码。
#include<stdio.h>
int main()
{
printf("hello world!\n");
return 0;
}
我们知道在形成可执行程序之前我们要先通过链接动静态库。我们分别链接静态库和动态库观看可执行程序的属性。

我们发现通过静态库链接形成的可执行程序比通过动态库链接形成的可执行程序的内存大小要大。这是因为在汇编之后形成.o目标文件之后在链接静态库时,会将需要的静态库代码拷贝一份,会将.o文件和拷贝过来的代码整合最终形成可执行程序。但是在链接动态库时并不会拷贝动态库中的代码,只会在程序运行时,去动态库中链接所需要的代码。
所以静态链接形成的可执行程序在库文件丢失时不会受什么影响,但是它的体积大;动态链接形成的可执行程序在库文件丢失时会无法运行,但是它的体积小,但是linux下一般是默认的动态链接。我们通过file 可执行程序名称指令进行查看。
动态链接。
#include<stdio.h>
extern int add(int x,int y);
静态链接。

动静态库操作
制作静态库
上个标题我们讲述了动静态库的基本概念,下来我们要讲述如何制作静态库。
在制作静态库之前我们先看下述代码。
add.h
#include<stdio.h>
extern int add(int x,int y);
add.c
#include"add.h"
int add(int x,int y)
{
return x+y;
}
sub.h
#include<stdio.h>
extern int sub(int x,int y);
sub.c
#include"sub.h"
int sub(int x,int y)
{
return x-y;
}
test.c
#include"./sub.h"
#include"./add.h"
int main()
{
int x = 20;
int y = 30;
printf("+: %d\n",add(x,y));
printf("-: %d\n",sub(x,y));
return 0;
}
makefile
mytest:test.c
gcc -o $@ $^ ./add.c ./sub.c
.PHONY:clean
clean:
rm -f mytest
在编译形成可执行程序时,得告知代码依赖的源文件的目录。
运行之后,结果如下。

运行结果符合预期,但是本质上,我们在test.c的开始就引入了要使用的头文件,在gcc进行编译时就引入了要使用的源文件。这也符合我们以往的认知,但是有没有可能在形成可执行程序时,我们不用源文件,而使用源文件编译之后的文件呢。
答案当然是可以的。我们可以使用源文件编译之后形成的.o目标文件生成可执行程序。
对应的makefile如下。
obj=test.o add.o sub.o
mytest:$(obj)
gcc -o $@ $^
%.o:%.c
gcc -c $<
.PHONY:clean
clean:
rm -f mytest *.o
所以,为了源代码的安全性,我们在给别人代码时可以不给源代码,我们可以给原代码编译之后的.o文件,同样的也可以通过.o文件生成可执行程序。.o目标文件是二进制文件,进一步提升了源代码的安全性。

无论对于静态库还是动态库,库的本质都是由头文件和库文件组成的。库文件是由.o目标文件组成的集合,里面的内容为.h头文件中相关函数的实现。下来正式开始制作静态库。
同样有四个文件,add.c,add.h,sub.c,sub.h.
add.h
#include<stdio.h>
extern int add(int x,int y);
add.c
#include"add.h"
int add(int x,int y)
{
return x+y;
}
sub.h
#include<stdio.h>
extern int sub(int x,int y);
sub.c
#include"sub.h"
int sub(int x,int y)
{
return x-y;
}
库本身包含了头文件以及库文件,库文件由头文件对应的源文件经过汇编之后形成,所以对应的makefile文件为。
lib.a:sub.o add.o
ar -rc $@ $^
%.o:%.c
gcc -c $<
.PHONY:clean
clean:
rm -rf *.o lib.a output
.PHONY:output
output:
mkdir output
cp -rf *.h output
cp lib.a output
首先由.c文件通过gcc编译器在汇编之后称为.o文件,由.o文件生成了库文件本身,然后创建了一个目录output对应了静态库,将对应的头文件和库文件依次复制进去,最终这个output就是我们所称之的静态库。

使用静态库
有了静态库,我们究竟怎么使用呢?使用动态库就是用.o目标文件生成可执行程序,而不是用.c文件生成可执行程序。
创建了一个friend目录,在这个目录里包含了两个文件,一个是源文件test.c,一个是目录文件lib(里面为test.c要用的头文件和对应的静态库)。
friend目录下。

lib目录下。
test.c.
#include"add.h"
#include"sub.h"
int main()
{
int x = 20;
int y = 30;
printf("+:%d\n",add(x,y));
printf("-:%d\n",sub(x,y));
return 0;
}
使用相应的指令生成可执行程序。

其中-I表示要使用到的头文件的目录,-L表示要使用的静态库的目录,-l表示要使用的静态库的名称。无论是静态库还是动态库,库名称就是去掉lib前缀与.a或者.so后缀之后剩下的部分。
制作动态库
制作动态库的makefile如下。
libdy.so:sub.o add.o
gcc -shared -o $@ $^
%o:%c
gcc -fpic -c $<
.PHONY:clean
clean:
rm -f *.o lib.so lib
.PHONY:lib
lib:
mkdir lib
cp *.h lib
cp libdy.so lib
最终生成了libdy.so的动态库文件,但是动态库是由动态库文件和相关头文件组成的,动态库文件和动态库如下。


使用动态库
动态库的使用基本和静态库类似。
运行可执行程序,但是确报了错误。

为什么在链接完成静态库的时候运行可执行程序没有报这样的错误呢。因为程序在编译完之后,要把程序通过加载器加载到内存中运行。链接静态库时,将所有关联的库文件代码全部复制了下来并与本身的.o文件形成了可执行程序。所以在最终运行时就不需要再去找库文件的路径,直接就可以运行。 但是在链接动态库时,生成的可执行程序中并不包含动态库文件代码,在运行时就要找到对应的动态库代码所以就得有动态库的路径。
一般情况下我们是用export声明环境变量LD-LIBRARY_PATH导入动态库的路径,这样就可以在运行的时候找到动态库文件。


此时运行可执行程序,发现符合了预期的结果。
以上便是动态库和静态库的概念和操作。大家只需要记住一点,无论是动态还是静态库,其实其主要就是头文件(相关函数的声明)和相关的库文件(相关函数的实现),库文件就是由.o目标文件组成的,是头文件中相关函数的实现。
本期内容到此结束^_^

1204

被折叠的 条评论
为什么被折叠?



