先说说库文件是怎么来的吧。

以C为例,我们写一个程序,一般都不会把所有的函数都写在一个文件里面,通常都是划分模块,然后一个模块若干个文件,然后在main文件里面调用这些模块。我这里用一个magic.c文件代替实际程序中的所有的文件,你就当这个magic.c文件非常神奇,你调用里面的magic()函数后,它会自动按你的想法把剩下的工作都完成。下面是两个文件:

mian.c

int main(){
  magic();
}

magic.c:

#include <stdio.h>
void magic(){
  printf("This is a magic function\n");
}

main.c文件里面没有包括任何头文件,因为我们的编译是手动按步骤的,实际编码不推荐这样做,这里可以更加清楚的知道头文件的作用。

一般从C源文件到可执行代码要经过以下4个步骤:预编译(preprocess  gcc -E,生成.i文件),编译(compile gcc -S,生成.s文件),汇编(assemble  gcc -c,生成.o文件),链接(link  生成可执行文件)。这里只讨论最后两个步骤,汇编和链接。

汇编后的结果是每个源文件都有了对应的二进制代码;链接是把所有的二进制代码打包成一个文件,最后得到可执行文件。

使用这个命令汇编main.c:gcc -Wall -c main.c 

-Wall 是列出警告的开关,如果没有这个开关,汇编成功,什么提示都没有,如果打开这个开关,会得到如下一个警告:

main.c:2:3: warning: implicit declaration of function ‘magic’ [-Wimplicit-function-declaration]

要消除这个警告很容易,有两个办法:

1)前面加个声明就好了,void magic();  

2)写个magic.h的头文件:

void magic();

然后在main.c里面包含进来:#include "magic.h"。

这两种办法的作用是告诉main,现在没有magic的实现不要紧,我确定一定以及肯定这个magic函数是存在的,放心使用就好了,而且告诉了main这个magic该怎么用,参数是什么,返回值是什么。

但是没有magic的声明也没有问题,只是警告,不是错误,因为我们知道我们的magic是怎么定义的,而且我们也确定会在后面链接magic函数。

但是问题就出在这里了,比如我们写了一个超级牛逼的函数想让别人使用,但是这个超级牛逼的函数要下个礼拜才能给别人怎么办呢?我们可以先给个头文件,然后拍着胸脯说你就按照我这个函数声明写,只要你的调用(call)没问题,程序运行的结果就没问题。

一个礼拜后到了可以给别人我们的超级牛逼函数的时候了,这时候我们也有两种选择可以选。

第一,发源文件,这个没话说了,很棒,互相学习进步,让别人自己把源文件汇编成二进制代码,然后再和他的main代码链接成可执行代码就好了。

第二,如果涉及商业或者版权因素,那么我们就只能自己把汇编后的二进制代码给别人,然后让别人再去链接。这里又体现了头文件的好处,只需要知道函数怎么调用就可以了,不用去知道函数内部怎么写的。比如刚开始学C只需要知道怎么使用 fopen, fclose, fprintf, fscanf 等等就好了,等以后慢慢深入再去探个究竟。


好了,那么库文件究竟是什么呢?其实就是汇编后的二进制代码,是别人写好的超级牛逼的代码汇编以后放在那里让我们使用的。但是这个汇编后的二进制代码和我们自己生成的还有些不同,具体来说有两种。

库文件本身分为两种:静态库文件(static library)和动态库文件(dynamic library),linux下,静态库文件以.a结尾(archive),动态库文件以.so结尾(shared object)。

静态库文件其实就是汇编后的二进制代码的一个压缩文件(archive),里面可以有一个或者N个.o文件。比如上面的magic.c,可以这样生成静态库文件:

首先汇编:gcc -c magic.c ,生成magic.o,然后使用压缩文件命令(ar)生成静态库文件:ar rc libmagic.a magic.o ,其中rc是压缩选项,libmagic.a是给静态库文件起的名字,magic.o 就是用来生成静态库文件的二进制文件,当然后面可以接很多个.o文件,把他们压缩成一个静态库文件给别人使用。

libmagic.a 弄好以后就可以和main的二进制文件链接在一起了: gcc -o main main.o -lmagic -L.

-o main 选项是把产生的可执行文件命名为main,没有的话默认名字是a.out,-lmagic表示使用库文件libmagic.so(后面介绍)或者libmagic.a,如果都存在的话使用.so文件。现在还没有libmagic.so文件,所以链接了libmagic.a里面的magic函数。

动态库文件有些不同,直接使用gcc生成,首先还是汇编:gcc -fPIC -c magic.c ,因为汇编后的二进制代码要拿去做动态库,所以多了一个-fPIC选项,用来确定库中函数的链接位置。然后从二进制文件生成动态库文件:gcc -shared -o libmagic.so magic.o。后面可以接很多.o文件。

动态库的链接的命令和静态库一样,但是做的事情不一样。链接静态库文件是把库里面的函数复制了到了可执行文件里面,所以可执行文件生成以后有没有静态库文件就不重要了。而动态链接只是在可执行文件里面记录了那个函数需要使用的动态库文件,真正的链接是在运行(run)的时候,只有运行我们生成的可执行文件,到了要使用动态库文件里面函数的时候,那个函数才会被加载到内存中再执行。所以涉及到了操作系统寻找动态链接库的路径问题。可以使用ldd程序查看我们的程序都用到了哪些动态库文件: ldd main,输出如下:

linux-vdso.so.1 =>  (0x00007fff8c9dd000)

libmagic.so => not found

libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb0f846c000)

/lib64/ld-linux-x86-64.so.2 (0x00007fb0f8829000)

可以看到libmagic.so没有找到,所以如果运行main也会报错:./main: error while loading shared libraries: libmagic.so: cannot open shared object file: No such file or directory   。要让系统找到动态库文件就必须设置动态库文件的路径了(不同于可执行文件路径PATH,头文件路径INCLUDE 哦),如果要在动态库文件的搜寻路径里面加上当前路径,可以如下设置:

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.       /* 当前路径是. */

export LD_LIBRARY_PATH

设置好路径以后动态库文件就能被找到了,再运行ldd main,输出下面的结果:

linux-vdso.so.1 =>  (0x00007fffb55ff000)

libmagic.so (0x00007ff515fa7000)

libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff515bec000)

/lib64/ld-linux-x86-64.so.2 (0x00007ff5161ab000)

关于链接再多说一句吧,gcc的链接是调用了另一个程序:ld。可以自己手动调用ld或者写在Makefile里面,不过一般不用那么麻烦,用gcc就好了。

由于链接方法不用,所以链接静态库文件后的可执行文件一般都比较大,比如上面的例子,链接静态库文件后的main大小是8498个字节,而链接动态库文件后的main的大小是8401字节。好像文件大小没少多少,那是因为我们的magic函数小,大一点区别就很明显了。


谢谢观赏,欢迎点评。


参考文献:

The art of debugging with gdb,ddd, and eclipse. Norman Matloff and Peter Jay Salzman. 


GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐