静态库/动态库的编译和使用方法学习记录

本文参考资料:《Linux编程一站式学习》 版权 © 2008, 2009 宋劲杉, 北京亚嵌教育研究中心
部份内容摘自此文
linux

有时候须要把一组代码编译成一个库,这个库在不少项目中都要用到,例如libc就是这样一个库,咱们在不一样的程序中都会用到libc中的库函数(例如printf),也会用到libc中的变量。
学习中用到的一个简单的小程序,程序将字符a,b,c压入堆栈stack[512],随后再倒序输出,打印出cba。为了使用到的gcc命令更具备意义,特地将程序分开在了几个C文件中,最后将几个.o文件连接为可执行文件。
stack.c编程

char stack[512];
int top = -1;

pop.c小程序

extern int top;
extern char stack[512];
char pop(void)
{
return stack[top--];
}

push.cide

extern int top;
extern char stack[512];
void push(char c)
{
stack[++top]=c;
}


empty.cwordpress

extern int top;
int is_empty(void)
{
return top == -1;
}

stack.h函数

#ifdef STACK_H
#define STACK_H
extern void push(char);
extern char pop(void);
extern int is_empty(void);
#endif

main.c学习

#include
  
  
  
  
#include "stack.h"
int main(void)
{
push('a');
push('b');
push('c');
while(!is_empty())
putchar(pop());
putchar('\n');
return 0;
}

整个程序放在/root/ctest/stack下,文件结构以下:索引

[root@localhost stack]pwd
/root/ctest/stack
[root@localhost stack]tree
.
|-- main.c
`-- stack
|-- empty.c
|-- pop.c
|-- push.c
|-- stack.c
|-- stack.h接口

我将除了main.c外的其余文件放入stack目录中,并准备将stack目录下的全部东西打包为库文件,也就是将几个函数打包。ip

静态库

进入stack目录编译并生成libstack.a文件,再编译main.c,生成可执行文件main:

[root@localhost]pwd
/root/ctest/stack/stack
[root@localhost]gcc -c empty.c pop.c push.c stack.c
[root@localhost]ar rs libstack.a empty.o pop.o push.o stack.o
ar:creating libstack.a
[root@localhost]cd ..
[root@localhost]gcc main.c -Lstack -lstack -Istack -omain
[root@localhost]./main
cba

最后的文件结构就变成了:

[root@localhost stack]pwd
/root/ctest/stack
[root@localhost stack]tree
.
|-- main
|-- main.c
`-- stack
|-- empty.c
|-- empty.o
|-- libstack.a
|-- pop.c
|-- pop.o
|-- push.c
|-- push.o
|-- stack.c
|-- stack.h
|-- stack.o

库文件名都是以lib开头的,静态库以.a做为后缀,表示Archive。ar命令相似于tar命令,起一个打包的做用,可是把目标文件打包成静态库只能用ar命令而不能用tar命令。选项r表示将后面的文件列表添加到文件包,若是文件包不存在就建立它,若是文件包中已有同名文件就替换成新的。s是专用于生成静态库的,表示为静态库建立索引,这个索引被连接器使用。
gcc的-L参数指定了库文件的路径,-Lstack就是当前目录下的stack目录了,若是库文件在当前目录,用-L.就好;-lstack指定库文件名,正如刚才所说的库文件都是以lib开头,因此这里的参数库名不要加lib,也不要扩展名.a或.so(动态库),-Istack,若是你仔细看程序了会发现main.c里的#include "stack.h",stack.h在stack目录里,因此-Istack就指定了头文件的位置,固然若是这样写#include "stack/stack.h"的话,就不须要-Istack了。
另外,gcc使用-print-search-dirs参数能够查看你的gcc会在哪些目录里去查找库文件,除非你将刚才的libstack.a放在了这些位置,不然-L参数不可少。

动态库

动态库的区别与静态库,简单的说程序在运行时才会去查找库文件,而不是像静态库同样在连接时就将整个库链接到了可执行文件中,因此通常来讲,使用动态库的可执行文件要比使用静态库的体积小。
删除刚才产生的库文件,目标文件,可执行文件,只留下源码,执行下边的命令从新生成动态库及可执行文件:

[root@localhost]pwd
/root/ctest/stack
[root@localhost]cd stack
[root@localhost]gcc -c -fPIC *.c
[root@localhost]gcc -shared -Wl,-soname,libstack.so.1 -o libstack.so.1.0 *.o

这样,名为 libstack.so.1.0的动态库已经生成,可是连接是gcc只认识名为libstack.so的库文件,为何要加版本号呢,还有-Wl参数指定了libstack.so.1,这又是什么?咱们在系统的库文件目录里会看到不少的符号连接,这样作有什么意义?稍后解释。

如今须要建立一个符号连接:

[root@localhost]ln -s libstack.so.1.0 libstack.so

再编译main.c,生成可执行文件:

[root@localhost]cd ..
[root@localhost]gcc main.c -Lstack -lstack -Istack -o main

若是没有建立符号连接,在编译main.c的时候就会收到 /usr/bin/ld:can not find -lstack错误,连接器找不到stack库文件。
这个时候执行main会发生一个错误error while loading shared libraries: libstack.so: cannot open shared object file: No such file or directory。动态库没有找到。
生成的动态库在/root/ctest/stack/stack目录下,咱们在编译连接时指定的库的位置,可是运行时须要这个库文件,系统并不会找到/root/ctest/stack/stack里去。
用ldd命令就能够看到缺乏了哪些库:

[root@localhost]ldd main
linux-gate.so.1 => (0x00a4d000)
libstack.so => not found
libc.so.6 => /lib/libc.so.6 (0x00563000)
/lib/ld-linux.so.2 (0x00546000)

经过查看ld.so(8)的Man Page能够找到几种解决方法,这里使用其中一种。
修改/etc/ld.so.conf,其中加上库文件的路径/root/ctest/stack/stack/,执行ldconfig生成cache文件/etc/ld.so.cache:

[root@localhost]echo /root/ctest/stack/stack >> /etc/ld.so.conf
[root@localhost]ldconfig

这时libstack.so.1就产生了,标准的libstack.so其实应该连接到这个文件上来。
此时的main即可以执行:

[root@localhost]./main
cba

如今的文件结构应该是:

[root@localhost stack]pwd
/root/ctest/stack
[root@localhost stack]tree
.
|-- main
|-- main.c
`-- stack
|-- empty.c
|-- empty.o
|-- libstack.so -> libstack.so.1.0
|-- libstack.so.1 -> libstack.so.1.0
|-- libstack.so.1.0
|-- pop.c
|-- pop.o
|-- push.c
|-- push.o
|-- stack.c
|-- stack.h
|-- stack.o

能够将libstack.so删除,从新建立为指向libstack.so.1的连接,这是标准的作法。

[参考资料]共享库的命名惯例

按照共享库的命名惯例,每一个共享库有三个文件名:real name、soname和linker name。真正的库文件(而不是符号连接)的名字是real name,包含完整的共享库版本号。例如上面的libcap.so.1.十、libc-2.8.90.so等。

soname是一个符号连接的名字,只包含共享库的主版本号,主版本号一致便可保证库函数的接口一致,所以应用程序的.dynamic段只记录共享库的soname,只要soname一致,这个共享库就能够用。例如上面的libcap.so.1和libcap.so.2是两个主版本号不一样的libcap,有些应用程序依赖于libcap.so.1,有些应用程序依赖于libcap.so.2,但对于依赖libcap.so.1的应用程序来讲,真正的库文件不论是libcap.so.1.10仍是libcap.so.1.11均可以用,因此使用共享库能够很方便地升级库文件而不须要从新编译应用程序,这是静态库所没有的优势。注意libc的版本编号有一点特殊,libc-2.8.90.so的主版本号是6而不是2或2.8。

linker name仅在编译连接时使用,gcc的-L选项应该指定linker name所在的目录。有的linker name是库文件的一个符号连接,有的linker name是一段连接脚本。例如上面的libc.so就是一个linker name,它是一段连接脚本.

$ cat /usr/lib/libc.so
/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
OUTPUT_FORMAT(elf32-i386)
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.2 ) )

从新编译咱们的libstack,指定它的soname:

$ gcc -shared -Wl,-soname,libstack.so.1 -o libstack.so.1.0 stack.o push.o pop.o is_empty.o

这样编译生成的库文件是libstack.so.1.0,是real name,但这个库文件中记录了它的soname是libstack.so.1:

$ readelf -a libstack.so.1.0
......
Dynamic section at offset 0xf10 contains 22 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000e (SONAME) Library soname: [libstack.so.1]
......

若是把libstack.so.1.0所在的目录加入/etc/ld.so.conf中,而后运行ldconfig命令,ldconfig会自动建立一个soname的符号连接:

$ sudo ldconfig
$ ls -l libstack*
lrwxrwxrwx 1 root root 15 2009-01-21 17:52 libstack.so.1 -> libstack.so.1.0
-rwxr-xr-x 1 djkings djkings 10142 2009-01-21 17:49 libstack.so.1.0但这样编译连接main.c却会报错:

$ gcc main.c -L. -lstack -Istack -o main
/usr/bin/ld: cannot find -lstack

collect2: ld returned 1 exit status注意,要作这个实验,你得把先前编译的libstack共享库、静态库都删掉,若是先前拷到/lib或者/usr/lib下了也删掉,只留下libstack.so.1.0和libstack.so.1,这样你会发现编译器不认这两个名字,由于编译器只认linker name。能够先建立一个linker name的符号连接,而后再编译就没问题了:

$ ln -s libstack.so.1.0 libstack.so $ gcc main.c -L. -lstack -Istack -o main

相关文章
相关标签/搜索