当一段代码在一个程序中被多次使用的时候,我们可以把他写成函数来调用,当一类代码被多个程序重复使用的时候,我们就可以将其组建成一个库,来实现对这类代码的重复使用。
在对库进行修改的时候应该考虑其兼容性,也就是说,依赖于就的库的软件在将库更新后,这个软件还是可以使用的,不能因为库的更新导致软件的运行失败,否则的话,如果这个库非常的重要,那后果将会是灾难性的。那就意味着软件要重写。听别人说,linux的libc5过渡到libc6的时候是多么的麻烦,因为以来libc5的软件不能运行在libc6的库上,导致许多软件需要重新编译甚至重写。但是,这不能说,库不能被修改,而是说,在对库的修改和升级的时候要注意兼容。
对于库的命名,这里有些约定,但很简单,首先,所有的库都要以lib开头,gcc就依赖于这个约定,当你使用-l这个选项的时候,就会被默认的加上lib这个字符串。还有,以.a结尾表示静态库,以.so结尾的表示是共享库。另外还有就是编号的约定,一般格式为:库名.主版本号.次版本号.补丁级别号。对于编号的增加,约定是:若库达到了不能和前一个版本兼容了,就要升级主版本号,如果仅是增加了新的功能,但有和以前的版本兼容的话,那就是升级次版本号,若只是修正错误,则只需要升级补丁的级别。还有一类很特殊的库,他们以_p或者_g来结尾,通常,以_g结尾的库是调试库,编入了特殊的符号和功能,能够增加采用这个库的程序的调试能力,_p表示代码剖析库,他们包含的代码和符号能够进行复杂的代码剖析和性能分析。记住,当你使用这些特殊的库完成了程序的调试的时候,就要用,正常的库重新将软件进行编译。
对库的操作:nm, ar, ldd, ldconfig
nm命令
nm命令用来列出目标文件或者二进制文件所有的符号,他可以用来查看程序到底调用了什么函数,还能用来查看,某一个库中有没有我们所需要的函数。
nm [option(s)] [file(s)]
当你不指定文件的时候,nm就会寻找但前目录现有没有a.out文件,若有,则会将该文件作为要查看的文件,否则他会提示
nm: 'a.out': No such file
例如:
#include <stdio.h>
int i = 5;
char s;
const C = '0';
void fun(void)
{
printf("hello");
}
gcc -c 1.c后将会生成一个1.o的文件。然后我们利用nm工具来查看。
nm 1.o
00000000 R C
00000000 T fun
00000000 D i /*有一点不明白就是如果i被初始化为0,则他显示的是B,为初始化的*/
U printf
00000001 C s
其中的大写字母表示符号的种类,下表给出了他们的意思:
A The symbol’s value is absolute, and will not be changed by further linking.
B The symbol is in the uninitialized data section (known as BSS)
C The symbol is common
D The symbol is in the initialized data section.
G The symbol is in an initialized data section for small objects.
I The symbol is an indirect reference to another symbol
N The symbol is a debugging symbol
R The symbol is in a read only data section
S The symbol is in an uninitialized data section for small objects
T The symbol is in the text (code) section
U The symbol is undefined.
V The symbol is a weak object.
W The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.
- The symbol is a stabs symbol in an a.out object file
还有几个比较常用的选项:
-A 列出符号名的同时列出他来自那个文件。
-a 列出所有符号,包括调试符号
-l 列出对应的行号
-n 根据符号的地址来排序,默认是按名称来排的
-u 只列出未定义的符号(同--undefined-only,反--defined-only)
ar命令
ar命令使用来建立或修改备份文件,或是从备份文件中抽取文件,当然,也可以把他用在库的创建与修改上,他可以把多个文件按照一定的组织结构组成一个备份文件,而且所有的文件即使在备份文件中,仍然保存着其原来的权限与属性。组成他的文件称为他的member。
ar [emulation options] [-]{dmpqrstx}[abcfilNoPsSuvV] [member-name] [count] archive-file file...
ar -M [<mri-script]
常用的选项:
d 从库中删除模块
m 在一个库中移动成员
p 显示库中指定的成员到标准输出中。
q 快速追加,增加模块到库的结尾处,并不见查是否替换
r 在库中插入模块
t 显示库的模块的清单
x 提取库中的成员,若不指定,则提取所有的成员
a 在库中一个已存在的成员的后面添加一个文件
b 在库中一个已存在的成员的前面添加一个文件
c 创建一个库,不论库是否已经存在
f 在库中截断指定的名字
i 类似b
N 与count参数一起使用,在库中,有多个相同文件名的时候,指定提取或输出的个数。
o 当提取成员时,保留成员的原始数据
P 进行文件明匹配时使用全路径名
s 写入一个目标文件索引到库中或者更新一个存在的目标文件索引。
S 不创建目标文件索引
u ?
例如:我们有如下几个程序:
/*File 1.c*/
#include "test.h"
#include <stdio.h>
void p1(void)
{
printf("We are in p1 function\n");
}
/*File 2.c*/
#include "test.h"
#include <stdio.h>
void p2(void)
{
printf("We are in p2 function\n");
}
/*File test.h*/
#ifndef _TEST_
#define _TEST_
void p1(void);
void p2(void);
#endif
/*File 3.c*/
#include "test.h"
#include <stdio.h>
int main(void)
{
printf("We are in main function\n");
p1();
p2();
return 0;
}
他们之间的关系是:3.c掉用了1.c和2.c中的p1,p2函数,首先要编译1.c 2.c:
$gcc -c 1.c
$gcc -c 2.c
然后建立一个库,将产生的1.o 2.o放到库里面去:
$ar -rcs libtest.a 1.o 2.o
这就行了,然后使用gcc编译并连接3.c
$gcc 3.c -static -L. -l test
我们使用-static是为了将libtest.a和他连接起来,执行:
$./a.out
We are in main function
We are in p1 function
We are in p2 function
很明显,程序发的运行结果说明了,我们已经使用libtest.a这个库中的p1,p2函数。
ldd命令
将会列出为使程序正常运行所需要的共享库。
ldd [options] file
常用的选项,
-d 执行重定位,并报告所有丢失的函数
-r 执行对函数和数据对象的重定位,并报告丢失的任何函数或数据
ldconfig命令:
他是一个动态连接库的管理命令,能使动态连接库为系统所共享,他的工作是,在默认搜索目录也就是/lib和/usr/lib还有动态库配置文件/etc/ld.so.conf内所记录的目录中,搜索动态连接库,来创建动态装入程序ld.so所需的连接和缓存文件(/etc/ld.so.cache(保存已排序的动态连接库名)),程序连接时首先在cache中找,之后在到ld.so.conf找详细的路径。
共享库的编写
创建共享库的办法,和创建静态库的办法有点不同,我们仍然拿上面的程序当作例子:
1.编译1.o 2.o,注意,这里有不同的地方,再用gcc编译是,要用上-fPIC的选项,这能产生无关的代码,并能被加载到任何地址。
$gcc -fPIC -c 1.c
$gcc -fPIC -c 2.c
2.用ar创建库,注意,这个时候的文件名有所改变
ar -rcs libtest.so 1.o 2.o
3.使用gcc的-shared的选项和-soname选项,用-Wl选项把参数传递连接器ld,使用-l选项显示的链接C库,以保证得到所需的启动(startup)代码,从而避免程序在使用不同的,可能不兼容版本的C库的系统上不能运行。
$ gcc -g -shared -Wl,-soname,libtest.so -o libtest.so.1.0.0 1.o 2.o -lc
之后会产生一个libtest.so.1.0.0文件,
我们只是为了测试并不想 把他安装到系统上面,所以要为其建立连接:(用于soname)
$ ln -s libtest.so.1.0.0 libtest.so.1
另一个是链接程序在使用-l test连接到libtest使用的:
$ ln -s libtest.so.1.0.0 libtest.so
为了使用刚才建立的共享库,我们需要将libtest.so拷贝到/usr/lib的目录下面:
$cp libtest.so /usr/lib
然够编译3.c:
$gcc 3.c -l test
$./a.out
We are in main function
We are in p1 function
We are in p2 function
还有一种方法是把他的路径放到/etc/ld.so.conf文件中,然后以root的身份运行一下ldconfig命令,要说的是,ldconfig在/sbin中。
dl接口
加载共享对象:
void * dlopen (const char *filename, int flag);
他会以flag指定的模式加载filename指定的对象,若filename是NULL,则dlopen打开当前执行的文件,如果是一个绝对的路径名,dlopen就会打开那个文件,如果仅仅是一个文件名,则dlopen会以下面给定的顺序搜索下列目录,查找文件$LD_ELF_LIBRARY_PATH:
$LD_LIBRARY_PATH, /etc/ld.so.cache, /usr/lib/, /lib
参数flag:
RTLD_LAZY : 来自被加载的对象的符号在被调用时解析
RTLD_NOW : 来自被加载的对象的所有符号在函数dlopen返回前解析
如果他们的其中一个 | RTDL_GLOBAL 就会导致导出所有的符号,就像他们被直接链接一样;
另外,当dlopen成功返回时,返回的是一个句柄,否则返回NULL;
使用共享对象:
void *dlsym(void *handle, const char *symbol);
dlsym在handle中搜索symbol,失败返回NULL;
错误检查
char *dlerror(void);
返回描述最近的发生错误的字符串,没错时返回NULL;
卸载共享对象:
int dlclose(void *handle);
顺便说一下,他们所在的头文件是dlfcn.h
例如:
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
void * handle;
void (*pfun)(void);
char *err;
printf("we are in function main\n");
if (NULL == (handle = dlopen("./libtest.so",RTLD_NOW)))
{
printf("Failed open libteat\n");
return 1;
}
dlerror();
pfun = dlsym(handle,"p1");
printf("%s\n",((err = dlerror()) == NULL) ? "get p1" : "err");
(*pfun)();
pfun = dlsym(handle,"p2");
printf("%s\n",((err = dlerror()) == NULL) ? "get p2" : "err");
(*pfun)();
dlclose(handle);
return 0;
}
将其保存为4.c后:
$gcc -Wall 4.c -l dl
$./a.out
we are in function main
get p1
We are in p1 function
get p2
We are in p2 function
在对库进行修改的时候应该考虑其兼容性,也就是说,依赖于就的库的软件在将库更新后,这个软件还是可以使用的,不能因为库的更新导致软件的运行失败,否则的话,如果这个库非常的重要,那后果将会是灾难性的。那就意味着软件要重写。听别人说,linux的libc5过渡到libc6的时候是多么的麻烦,因为以来libc5的软件不能运行在libc6的库上,导致许多软件需要重新编译甚至重写。但是,这不能说,库不能被修改,而是说,在对库的修改和升级的时候要注意兼容。
对于库的命名,这里有些约定,但很简单,首先,所有的库都要以lib开头,gcc就依赖于这个约定,当你使用-l这个选项的时候,就会被默认的加上lib这个字符串。还有,以.a结尾表示静态库,以.so结尾的表示是共享库。另外还有就是编号的约定,一般格式为:库名.主版本号.次版本号.补丁级别号。对于编号的增加,约定是:若库达到了不能和前一个版本兼容了,就要升级主版本号,如果仅是增加了新的功能,但有和以前的版本兼容的话,那就是升级次版本号,若只是修正错误,则只需要升级补丁的级别。还有一类很特殊的库,他们以_p或者_g来结尾,通常,以_g结尾的库是调试库,编入了特殊的符号和功能,能够增加采用这个库的程序的调试能力,_p表示代码剖析库,他们包含的代码和符号能够进行复杂的代码剖析和性能分析。记住,当你使用这些特殊的库完成了程序的调试的时候,就要用,正常的库重新将软件进行编译。
对库的操作:nm, ar, ldd, ldconfig
nm命令
nm命令用来列出目标文件或者二进制文件所有的符号,他可以用来查看程序到底调用了什么函数,还能用来查看,某一个库中有没有我们所需要的函数。
nm [option(s)] [file(s)]
当你不指定文件的时候,nm就会寻找但前目录现有没有a.out文件,若有,则会将该文件作为要查看的文件,否则他会提示
nm: 'a.out': No such file
例如:
#include <stdio.h>
int i = 5;
char s;
const C = '0';
void fun(void)
{
printf("hello");
}
gcc -c 1.c后将会生成一个1.o的文件。然后我们利用nm工具来查看。
nm 1.o
00000000 R C
00000000 T fun
00000000 D i /*有一点不明白就是如果i被初始化为0,则他显示的是B,为初始化的*/
U printf
00000001 C s
其中的大写字母表示符号的种类,下表给出了他们的意思:
A The symbol’s value is absolute, and will not be changed by further linking.
B The symbol is in the uninitialized data section (known as BSS)
C The symbol is common
D The symbol is in the initialized data section.
G The symbol is in an initialized data section for small objects.
I The symbol is an indirect reference to another symbol
N The symbol is a debugging symbol
R The symbol is in a read only data section
S The symbol is in an uninitialized data section for small objects
T The symbol is in the text (code) section
U The symbol is undefined.
V The symbol is a weak object.
W The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.
- The symbol is a stabs symbol in an a.out object file
还有几个比较常用的选项:
-A 列出符号名的同时列出他来自那个文件。
-a 列出所有符号,包括调试符号
-l 列出对应的行号
-n 根据符号的地址来排序,默认是按名称来排的
-u 只列出未定义的符号(同--undefined-only,反--defined-only)
ar命令
ar命令使用来建立或修改备份文件,或是从备份文件中抽取文件,当然,也可以把他用在库的创建与修改上,他可以把多个文件按照一定的组织结构组成一个备份文件,而且所有的文件即使在备份文件中,仍然保存着其原来的权限与属性。组成他的文件称为他的member。
ar [emulation options] [-]{dmpqrstx}[abcfilNoPsSuvV] [member-name] [count] archive-file file...
ar -M [<mri-script]
常用的选项:
d 从库中删除模块
m 在一个库中移动成员
p 显示库中指定的成员到标准输出中。
q 快速追加,增加模块到库的结尾处,并不见查是否替换
r 在库中插入模块
t 显示库的模块的清单
x 提取库中的成员,若不指定,则提取所有的成员
a 在库中一个已存在的成员的后面添加一个文件
b 在库中一个已存在的成员的前面添加一个文件
c 创建一个库,不论库是否已经存在
f 在库中截断指定的名字
i 类似b
N 与count参数一起使用,在库中,有多个相同文件名的时候,指定提取或输出的个数。
o 当提取成员时,保留成员的原始数据
P 进行文件明匹配时使用全路径名
s 写入一个目标文件索引到库中或者更新一个存在的目标文件索引。
S 不创建目标文件索引
u ?
例如:我们有如下几个程序:
/*File 1.c*/
#include "test.h"
#include <stdio.h>
void p1(void)
{
printf("We are in p1 function\n");
}
/*File 2.c*/
#include "test.h"
#include <stdio.h>
void p2(void)
{
printf("We are in p2 function\n");
}
/*File test.h*/
#ifndef _TEST_
#define _TEST_
void p1(void);
void p2(void);
#endif
/*File 3.c*/
#include "test.h"
#include <stdio.h>
int main(void)
{
printf("We are in main function\n");
p1();
p2();
return 0;
}
他们之间的关系是:3.c掉用了1.c和2.c中的p1,p2函数,首先要编译1.c 2.c:
$gcc -c 1.c
$gcc -c 2.c
然后建立一个库,将产生的1.o 2.o放到库里面去:
$ar -rcs libtest.a 1.o 2.o
这就行了,然后使用gcc编译并连接3.c
$gcc 3.c -static -L. -l test
我们使用-static是为了将libtest.a和他连接起来,执行:
$./a.out
We are in main function
We are in p1 function
We are in p2 function
很明显,程序发的运行结果说明了,我们已经使用libtest.a这个库中的p1,p2函数。
ldd命令
将会列出为使程序正常运行所需要的共享库。
ldd [options] file
常用的选项,
-d 执行重定位,并报告所有丢失的函数
-r 执行对函数和数据对象的重定位,并报告丢失的任何函数或数据
ldconfig命令:
他是一个动态连接库的管理命令,能使动态连接库为系统所共享,他的工作是,在默认搜索目录也就是/lib和/usr/lib还有动态库配置文件/etc/ld.so.conf内所记录的目录中,搜索动态连接库,来创建动态装入程序ld.so所需的连接和缓存文件(/etc/ld.so.cache(保存已排序的动态连接库名)),程序连接时首先在cache中找,之后在到ld.so.conf找详细的路径。
共享库的编写
创建共享库的办法,和创建静态库的办法有点不同,我们仍然拿上面的程序当作例子:
1.编译1.o 2.o,注意,这里有不同的地方,再用gcc编译是,要用上-fPIC的选项,这能产生无关的代码,并能被加载到任何地址。
$gcc -fPIC -c 1.c
$gcc -fPIC -c 2.c
2.用ar创建库,注意,这个时候的文件名有所改变
ar -rcs libtest.so 1.o 2.o
3.使用gcc的-shared的选项和-soname选项,用-Wl选项把参数传递连接器ld,使用-l选项显示的链接C库,以保证得到所需的启动(startup)代码,从而避免程序在使用不同的,可能不兼容版本的C库的系统上不能运行。
$ gcc -g -shared -Wl,-soname,libtest.so -o libtest.so.1.0.0 1.o 2.o -lc
之后会产生一个libtest.so.1.0.0文件,
我们只是为了测试并不想 把他安装到系统上面,所以要为其建立连接:(用于soname)
$ ln -s libtest.so.1.0.0 libtest.so.1
另一个是链接程序在使用-l test连接到libtest使用的:
$ ln -s libtest.so.1.0.0 libtest.so
为了使用刚才建立的共享库,我们需要将libtest.so拷贝到/usr/lib的目录下面:
$cp libtest.so /usr/lib
然够编译3.c:
$gcc 3.c -l test
$./a.out
We are in main function
We are in p1 function
We are in p2 function
还有一种方法是把他的路径放到/etc/ld.so.conf文件中,然后以root的身份运行一下ldconfig命令,要说的是,ldconfig在/sbin中。
dl接口
加载共享对象:
void * dlopen (const char *filename, int flag);
他会以flag指定的模式加载filename指定的对象,若filename是NULL,则dlopen打开当前执行的文件,如果是一个绝对的路径名,dlopen就会打开那个文件,如果仅仅是一个文件名,则dlopen会以下面给定的顺序搜索下列目录,查找文件$LD_ELF_LIBRARY_PATH:
$LD_LIBRARY_PATH, /etc/ld.so.cache, /usr/lib/, /lib
参数flag:
RTLD_LAZY : 来自被加载的对象的符号在被调用时解析
RTLD_NOW : 来自被加载的对象的所有符号在函数dlopen返回前解析
如果他们的其中一个 | RTDL_GLOBAL 就会导致导出所有的符号,就像他们被直接链接一样;
另外,当dlopen成功返回时,返回的是一个句柄,否则返回NULL;
使用共享对象:
void *dlsym(void *handle, const char *symbol);
dlsym在handle中搜索symbol,失败返回NULL;
错误检查
char *dlerror(void);
返回描述最近的发生错误的字符串,没错时返回NULL;
卸载共享对象:
int dlclose(void *handle);
顺便说一下,他们所在的头文件是dlfcn.h
例如:
#include <stdio.h>
#include <dlfcn.h>
int main(void)
{
void * handle;
void (*pfun)(void);
char *err;
printf("we are in function main\n");
if (NULL == (handle = dlopen("./libtest.so",RTLD_NOW)))
{
printf("Failed open libteat\n");
return 1;
}
dlerror();
pfun = dlsym(handle,"p1");
printf("%s\n",((err = dlerror()) == NULL) ? "get p1" : "err");
(*pfun)();
pfun = dlsym(handle,"p2");
printf("%s\n",((err = dlerror()) == NULL) ? "get p2" : "err");
(*pfun)();
dlclose(handle);
return 0;
}
将其保存为4.c后:
$gcc -Wall 4.c -l dl
$./a.out
we are in function main
get p1
We are in p1 function
get p2
We are in p2 function