一、基本概念
1.1
什么是库
在
windows
平台和
linux
平台下都大量存在着库。
本质上来说库是 一种可执行代码的二进制形式,可以被操作系统载入内存执行。
由于
windows
和
linux
的平台不同(主要是编译器、汇编器和连接器 的不同),因此二者库的二进制是不兼容的。
本文仅限于介绍
linux
下的库。
1.2
库的种类
linux
下的库有两种:静态库和共享库(动态库)。
二者的不同点在于代码被载入的时刻不同。
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。
1.3
库存在的意义
库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。
现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻 常。
共享库的好处是,不同的应用程序如果调用相同的库,那么在 内存里只需要有一份该共享库的实例。
1.4
库文件是如何产生的在
linux
下
静态库的后缀是
.a
,它的产生分两步
Step 1.
由源文件编译生成一堆
.o ,每个
.o
里都包含这个编译单元的符号表
Step 2.ar
命令将很多
.o 转换成
.a
,成文静态库
动态库的后缀是
.so
,它由
gcc 加特定参数编译产生。
具体方法参见后文实例。
1.5
库文件是如何命名的,有没有什么规范
在
linux
下,库文件一般放在
/usr/lib
和
/lib
下,
静态库的名字一般为
libxxxx.a
,其中
xxxx 是该
lib
的名称
动态库的名字一般为
libxxxx.so.major.minor
,
xxxx 是该
lib
的名称,
major
是主版本号,
minor
是副版本号
1.6
如何知道一个可执行程序依赖哪些库
ldd
命令可以查看一个可执行程序依赖的共享库,
例如
# ldd /bin/lnlibc.so.6
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
可以看到
ln
命令依赖于
libc
库和
ld-linux
库
1.7
可执行程序在执行的时候如何定位共享库文件
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径
此时就需要系统动态载入器
(dynamic linker/loader)
对于
elf
格式的可执行程序,是由
ld-linux.so*
来完成的,它先后搜索
elf 文件的
DT_RPATH
段—环境变量
LD_LIBRARY_PATH
—
/etc/ld.so.cache
文件列表—
/lib/,/usr/lib
目录找到库文件后将其载入内存
如:
export LD_LIBRARY_PATH=’pwd’
将当前文件目录添加为共享目录
1.8
在新安装一个库之后如何让系统能够找到他
如果安装在
/lib
或者
/usr/lib
下,那么
ld 默认能够找到,无需其他操作。
如果安装在其他目录,需要将其添加到
/etc/ld.so.cache
文件中,步骤如下
1.
编辑
/etc/ld.so.conf
文件,加入库文件所在目录的路径
2.
运行
ldconfig
,该命令会重建
/etc/ld.so.cache
文件
二、用gcc生成静态和动态链接库的示例
我们通常把一些公用函数制作成函数库,供其它程序使用。
函数库分为
静态库
和
动态库
两种。
静态库在程序编译时会被连接到目标代码 中,程序运行时将不再需要该静态库。
动态库在程序编译时并不会被连接到目标 代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
本文主要通过举例来说明在
Linux
中如何创建静态库和动态库,以及使用它们。
为了便于阐述,我们先做一部分准备工 作。
2.1
准备好测试代码
hello.h
、
hello.c
和
main.c
;
hello.h(
见程序
1)
为该函数库的头文件。
hello.cpp(
见程序
2)
是函数库的源程序,其中包含公用函数
hello
,该函数将在屏幕上输出
"Hello XXX!"
。
main.cpp(
见程序
3)
为测试库文件的主程序,在主程序中调用了公用函数
hello
。
程序
1: hello.h
|
|
程序3:main.cpp
|
2.2
问题的提出
注意:这个时候,我 们编译好的
hello.o
是无法通过
gcc –o
编译的,这个道理非常简单,
hello.c
是一个没有
main
函数的
.c
程序,因此不够成一个完整的程序,如果使用
gcc –o
编译并连接它,
GCC
将报错。
无论静态库,还是动态库,都是由
.o
文件创建的。因此,我们必须将源程序
hello.c
通过
gcc
先编译成
.o
文件。
这个时候我们有 三种思路:
1)
通过编译多个源文 件,直接将目标代码合成一个
.o
文件。
2)
通过创建静态链接库
libmyhello.a
,使得
main
函数调用
hello
函数时可调用静态链接库。
3)
通过创建动态链接库
libmyhello.so
,使得
main
函数调用
hello
函数时可调用静态链接库 。
2.3
思路一:编译多个源文件
在系统提示符下键入以下命令得到
hello.o
文件。
# g++ -c hello.cpp
为什么不适用
g++–o hello hello.cpp
这个道理 我们之前已经说了,使用
-c
是什么意 思呢?这涉及到
g++
编译选项 的常识。
我们通常使用的
g++ –o
是将
.cpp
源文件编 译成为一个可执行的二进制代码,这包括调用作为
GCC
内的一部 分真正的
C
编译器(
ccl
),以及 调用
GNU C
编译器的 输出中实际可执行代码的外部
GNU
汇编器和 连接器工具。
而
g++ –c
是使用
GNU
汇编器将 源文件转化为目标代码之后就结束,在这种情况下连接器并没有被执行,所以输出的目标文件不会包含作为
Linux
程序在被 装载和执行时所必须的包含信息,但它可以在以后被连接到一个程序。
我们运行
ls
命令看看 是否生存了
hello.o
文件。
# ls
hello.cpp hello.h hello.o main.cpp
在
ls
命令结果 中,我们看到了
hello.o
文件,本 步操作完成。
同理编译
main
#g++ –c main.cpp