linux下创建库函数

来源:

在Linux下如何使用自己的库函数-riverok-ChinaUnix博客 http://blog.chinaunix.net/uid-21393885-id-88128.html

构建Linux下的函数库编译方案 - observer的日志 - 网易博客 http://blog.163.com/[email protected]/blog/static/23180765201111622915148/

linux下的函数库 程序 编译_ablab_新浪博客 http://blog.sina.com.cn/s/blog_557366df0100l5m1.html

1 为什么要使用库?

关于代码复用的途径,一般有两种。

粘贴复制

这是最没有技术含量的一种方案。如果代码小,则工作量还可以忍受,如果代码很庞大,则此法不可取。即便有人原意这样做,但谁又能保证所有的代码都可得到呢?

而库的出现很好的解决了这个问题。

使用函数库

库,是一种封装机制,简单说把所有的源代码编译成目标代码后打成的包。

那么用户怎么能知道这个库提供什么样的接口呢?难道要用nm等工具逐个扫描?

不用担心,库的开发者早以把一切都做好了。除了包含目标代码的库外,www.Linuxidc.com一般还会提供一系列的头文件,头文件中就包含了库的接口。为了让方便用户,再加上一个使用说明就差不多完美了。

2 库的分类

2.1 库的分类

根据链接时期的不同,库又有静态库和动态库之分。

(1) 静态库

一般以*.a命名。

程序编译时被加载,此后,只要程序不被重新编译,静态库就没有作用了(可以删掉)。

由于静态库的代码在编译过程中已经被载入可执行程序,因此体积较大,如果有多个应用程序都用了同一个静态库,在存放可执行程序的硬盘中就会有这个静态库的多份拷贝。如果他们同时在运行,那么在内存中也会有这个静态库的多份拷贝。但是如下面提到的动态库相比较,程序执行时间比较短,因为没有执行时库函数的加载。所谓“以空间换时间”。

下面我们用一个实例说明静态库的编程和使用。

//库函数:hellowlib.c

#include

void printhellow()

{

printf("hellow,now in lib routine\n");

return ;

}

首先生成目标文件:gcc -c  printhellow.c  –o printhellow.o

然后使用ar(archive)命令把目标文件制作库文件:ar cqs libhello.a printhellow.o

注意库文件名一定是lib***.a格式,不要忘了加lib作为前缀。

下面我们写一个程序调用静态库libhello.a中的printhellow函数。

//testlib.c

int main(int arc, char **argv)

{

printhellow();

return ;

}

下面编译:gcc -o testlib testlib.c -L ./  -lhello

即可生成可执行文件testlib。

注意上面的-L(大写)指示库的路径在当前目录下。如果没有这个选项,就需要把库libhello.a加入到标准路径中。如/usr/lib中。-l(小写)只需要跟hello,其他字符全部不要,否则出错。

(2) 动态库(共享库)

一般以.so命名(share object)

与静态库不同,共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。与上面提到的静态库相比,很是节约空间。但运行时需要载入,因此运行时间相对静态库而言比较长。所谓“以时间换空间”。

动态链接的意思就是在程序装载内存的时候才真正的把库函数代码链接进行确定它们的地址,并且就算有几个程序同时运行,内存也只存在一份函数代码。

动态库的代码要实现这样的功能,必须满足这样一种条件:能够被加载到不同进程的不同地址,所以代码要经过特别的编译处理,我们把这种经过特别处理的代码叫做“位置无关代码(Position independed Code .PIC)”。

位置无关代码可以这样看,内存中的动态代码只有一份副本,但动态库的数据却可能有多份。由于每一个链接到动态的进程都可能会修改库的数据,所以每当有这种情况,操作系统就复制出一份数据副本,然后修改进程的地址空间映射,使它指向新的数据副本,这样,进程最后修改的只是属于自己的那份数据。

更详细来说,某个程序的在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有才链接载入。这样的模式虽然会带来一些“动态链接”额外的开销,却大大的节省了系统的内存资源。我们所知C的标准库就是动态链接库,即系统中所有运行的程序共享着同一个C标准库的代码段。

正如刚才所说,由于动态链接库函数不会被拷贝到可执行文件中。编译的时编译器只会做一些函数名之类的检查。程序运行时,被调用的动态链接库函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须是相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器,这些对象文件是用来做动态链接库的,所以要用地址无关代码(Position Independent Code (PIC))。

共享库又可分为动态链接(Dynamic linking)和动态加载(Dynamic loading)两种。

 动态链接(Dynamic linking)

在编译程序时指定要连接的库,然后再程序运行时一开始就将库载入。这也称为隐式调用库函数。

示例仍然使用钢材程序,编译库:

gcc -fpic -shared -o libhello.so printhellow.c

-fpic便指示了这是地址无关代码,-shared指示是一个共享库

编译程序,和静态库使用方法一样:gcc -o testlib testlib.c -L ./ -lhello

这时候编译成功,但我们执行testlib却发生以下错误:

./testlib: error while loading shared libraries: libhello.so: cannot load shared object file: No such file or directory

显然,程序加载时到标准路径中找不到库文件。

解决办法 1)把我们生成的库放到标准路径中去即可。

mv libhello.so  /usr/lib

2)建立符号连接 ln -s `pwd`/libhello.so /usr/lib/libhello.so

3)将动态链接库所在目录名追加到动态链接库配置文件/etc/ld.so.conf中

pwd >> /etc/ld.so.conf

ldconfig

4)利用动态链接库管理命令ldconfig,强制其搜索指定目录,并更新缓存文件,# ldconfig `pwd`

但是这种方法只是暂时的,如果再次运行ldconfig, 缓存文件内容可能改变,所需的动态链接库可能不被系统共享了。

5)编译时指定库的加载路径: gcc -o testlib testlib.c -L ./ -lhello -Wl, -rpath ./

这里, -rpath 说明了程序运行时库的加载路径,因为-rpath是ld命令的选项,所以gcc调用它时需要使用gcc的 -Wl选项。

 动态加载(Dynamic loading)

程序不会自动加载库,需要在程序中由程序员指定何时加载。系统提供了一套动态加载API以方便我们使用.

先看一下这些API:

1)

#include

void *dlopen( const char *file, int mode );

dlopen第一个参数是共享库的名称,会在以下路径中查找指定的共享库:

①环境变量LD_LIBRARY_PATH中指定的

②文件/etc/ld.so.cache中找到的库的列表,由ldconfig命令刷新。

③目录usr/lib。

④目录/lib。

⑤当前目录。

第二个参数为打开共享库的方式。有两个值

①RTLD_NOW:将共享库中的所有函数加载到内存

②RTLD_LAZY:稍后进行共享库中的函数的加载,调用dlsym()时加载某函数

2)

char *dlerror();

用dlerror()函数测试是否打开成功,并进行错误处理;

3)

void *dlsym( void *restrict handle, const char *restrict name );

用dlsym获得函数地址,存放在一个函数指针中,用获得的函数指针进行函数调用。

4)

char *dlclose( void *handle );

程序结束时用dlclose关闭打开的动态库,防止资源泄露。

(3)静态库和动态库的比较

链接静态库其实从某种意义上来说也是一种粘贴复制,只不过它操作的对象是目标代码而不是源码而已。因为静态库被链接后库就直接嵌入可执行文件中了,这样就带来了两个问题。

首先就是系统空间被浪费了。这是显而易见的,想象一下,如果多个程序链接了同一个库,则每一个生成的可执行文件就都会有一个库的副本,必然会浪费系统空间。

再者,人非圣贤,即使是精心调试的库,也难免会有错。一旦发现了库中有bug,挽救起来就比较麻烦了。必须一一把链接该库的程序找出来,然后重新编译。

动态库的出现正弥补了静态库的以上弊端。因为动态库是在程序运行时被链接的,所以磁盘上只须保留一份副本,因此节约了磁盘空间。如果发现了bug或要升级也很简单,只要用新的库把原来的替换掉就行了。

那么,是不是静态库就一无是处了呢?

答曰:非也非也。不是有句话么:存在即是合理。静态库既然没有湮没在滔滔的历史长河中,就必然有它的用武之地。想象一下这样的情况:如果你用libpcap库编了一个程序,要给被人运行,而他的系统上没有装pcap库,该怎么解决呢?最简单的办法就是编译该程序时把所有要链接的库都链接它们的静态库,这样,就可以在别人的系统上直接运行该程序了。

所谓有得必有失,正因为动态库在程序运行时被链接,故程序的运行速度和链接静态库的版本相比必然会打折扣。然而瑕不掩瑜,动态库的不足相对于它带来的好处在现今硬件下简直是微不足道的,所以链接程序在链接时一般是优先链接动态库的,除非用-static参数指定链接静态库。

(4)如何判断一个程序有没有链接动态库?

答案是用file实用程序。

file程序是用来判断文件类型的,在file命令下,所有文件都会原形毕露的。

顺便说一个技巧。有时在 windows下用浏览器下载tar.gz或tar.bz2文件,后缀名会变成奇怪的tar.tar,到Linux有些新手就不知怎么解压了。但 Linux下的文件类型并不受文件后缀名的影响,所以我们可以先用命令file xxx.tar.tar看一下文件类型,然后用tar加适当的参数解压。

另外,还可以借助程序ldd实用程序来判断。

ldd是用来打印目标程序(由命令行参数指定)所链接的所有动态库的信息的,如果目标程序没有链接动态库,则打印“not a dynamic executable”,ldd的用法请参考manpage。

3 创建自己的库

下面是testdyn.c程序实例:

#include

#include

#include

int main(int arc, char **argv)

{

void (*pfunc)(); /* pointer of function */

char *perr;

/* load shared library dynamicly */

void *handle = dlopen("libhello.so", RTLD_LAZY);

if(NULL == handle)

{

printf("dlopen error,errno = %d\n",errno);

return -1;

}

perr = dlerror();

if(NULL != perr)

{

printf("dlerror first \n");

return -1;

}

/* get the address of printhellow function */

pfunc = dlsym(handle, "printhellow");

perr = dlerror();

if(NULL != perr)

{

printf("dlerror when dlsym \n");

return -1;

}

/* call the function */

(*pfunc)();

dlclose(handle);

printf("load share library success!\n") ;

return -1;

}

编译:gcc -o testdyn testdyn.c –ldl

注意加上–ldl参数。

程序运行:

[[email protected] fengxz]# ./testdyn

hellow,now in lib routine

load share library success!

以hello.c为例

创建文件hello.c,内容如下:

#include

void hello(void)

{

printf("Hello World\n");

}

用命令gcc -shared hello.c -o libhello.so编译为动态库。可以看到,当前目录下多了一个文件libhello.so。

[[email protected] test]$ file libhello.so

libhello.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped

看到了吧,文件类型是shared object了。

再编辑一个测试文件test.c,内容如下:

int

main()

{

hello();

return 0;

}

这下可以编译了:)

[[email protected] test]$ gcc test.c

/tmp/ccm7w6Mn.o: In function `main‘:

test.c:(.text+0x1d): undefined reference to `hello‘

collect2: ld returned 1 exit status

链接时gcc找不到hello函数,编译失败:(。原因是hello在我们自己创建的库中,如果gcc能找到那才教见鬼呢!ok,再接再厉。

[[email protected] test]$ gcc test.c -lhello

/usr/lib/gcc/i686-pc-Linux-gnu/4.0.0/../../../../i686-pc-Linux-gnu/bin/ld: cannot find -lhello

collect2: ld returned 1 exit status

[[email protected] test]$ gcc test.c -lhello -L.

[[email protected] test]$

第一次编译直接编译,gcc默认会链接标准c库,但符号名hello解析不出来,故连接阶段通不过了。

现在用gcc test.c -lhello -L.已经编译成功了,默认输出为a.out。现在来试着运行一下:

[[email protected] test]$ ./a.out

./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory

咦,怎么回事?原来虽然链接时链接器(dynamic linker)找到了动态库libhello.so,但动态加载器(dynamic loader, 一般是/lib/ld-Linux.so.2)却没找到。再来看看ldd的输出:

[[email protected] test]$ ldd a.out

Linux-gate.so.1 => (0xffffe000)

libhello.so => not found

libc.so.6 => /lib/libc.so.6 (0x40034000)

/lib/ld-Linux.so.2 (0x40000000)

果然如此,看到没有,libhello.so => not found。

Linux为我们提供了两种解决方法:

1.可以把当前路径加入 /etc/ld.so.conf中然后运行ldconfig,或者以当前路径为参数运行ldconfig(要有root权限才行)。

2.把当前路径加入环境变量LD_LIBRARY_PATH中

当然,如果你觉得不会引起混乱的话,可以直接把该库拷入/lib,/usr/lib/等位置(无可避免,这样做也要有权限),这样链接器和加载器就都可以准确的找到该库了。

我们采用第二种方法:

[[email protected] test]$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PA

[[email protected] test]$ ldd a.out

Linux-gate.so.1 => (0xffffe000)

libhello.so => ./libhello.so (0x4001f000)

libc.so.6 => /lib/libc.so.6 (0x40036000)

/lib/ld-Linux.so.2 (0x40000000)

哈哈,这下ld-Linux.so.2就可以找到libhello.so这个库了。

现在可以直接运行了:

[[email protected] test]$ ./a.out

Hello World

3.2 创建静态库

仍使用刚才的hello.c和test.c。

第一步,生成目标文件。

[[email protected] test]$ gcc -c hello.c

[[email protected] test]$ ls hello.o -l

-rw-r--r-- 1 leo users 840 5月 6 12:48 hello.o

第二步,把目标文件归档。

[[email protected] test]$ ar r libhello.a hello.o

ar: creating libhello.a

OK,libhello.a就是我们所创建的静态库了,简单吧:)

[[email protected] test]$ file libhello.a

libhello.a: current ar archive

下面一行命令就是教你如何在程序中链接静态库的:

[[email protected] test]$ gcc test.c -lhello -L. -static -o hello.static

我们来用file命令比较一下用动态库和静态库链接的程序的区别:

[[email protected] test]$ gcc test.c -lhello -L. -o hello.dynamic

正如前面所说,链接器默认会链接动态库(这里是libhello.so),所以只要把上个命令中的 -static参数去掉就可以了。

用file实用程序验证一下是否按我们的要求生成了可执行文件:

[[email protected] test]$ file hello.static hello.dynamic

hello.static: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.6, statically linked, not stripped

hello.dynamic: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.6, dynamically linked (uses shared libs), not stripped

不妨顺便练习一下ldd的用法:

[[email protected] test]$ ldd hello.static hello.dynamic

hello.static:

not a dynamic executable

hello.dynamic:

Linux-gate.so.1 => (0xffffe000)

libhello.so => ./libhello.so (0x4001f000)

libc.so.6 => /lib/libc.so.6 (0x40034000)

/lib/ld-Linux.so.2 (0x40000000)

OK,看来没有问题,那就比较一下大小先:

[[email protected] test]$ ls -l hello.[ds]*

-rwxr-xr-x 1 leo users 5911 5月 6 12:54 hello.dynamic

-rwxr-xr-x 1 leo users 628182 5月 6 12:54 hello.static

看到区别了吧,链接静态库的目标程序和链接动态库的程序比起来简直就是一个庞然大物!

这么小的程序,很难看出执行时间的差别,不过为了完整起见,还是看一下time的输出吧:

[[email protected] test]$ time ./hello.static

Hello World

real 0m0.001s

user 0m0.000s

sys 0m0.001s

[[email protected] test]$ time ./hello.dynamic

Hello World

real 0m0.001s

user 0m0.000s

sys 0m0.001s

如果程序比较大的话,应该效果会很明显的。

时间: 2024-10-25 19:56:49

linux下创建库函数的相关文章

两种在linux下创建应用程序快捷方式的方法

两种在linux下创建应用程序快捷方式的方法: A. 在桌面上创建快捷方式 B. 在应用程序菜单中添加快捷方式 在桌面上创建快捷方式 这是最简单的一种方法,在桌面上单击鼠标右键,会有一个“创建启动器”栏.这里我以为mplayer创建快捷方式为例说明: 名称-mplayer(或者你喜欢的任何名称,这个名称会出现在快捷图标的 下方) 命令-/usr/bin/gmplayer(这个是mplayer的gui应用程序的执行文件,跟 安装路径相关,可以通过which gmplayer找到) 图标-一般应用程

4.windows和Linux下创建oracleusername表空间,表,插入数据,用户管理表等操作

进入超级管理员,运行下面命令 Window下创建数据库.表空间,用户,插入数据等操作 -- 01 创建表空间 -- 注意表空间的路径 依据实际安装环境进行调整 CREATE TABLESPACE ts_myscott LOGGING DATAFILE 'F:/app/to-to/oradata/orcl/ts_myscott.dbf' SIZE 10M EXTENT MANAGEMENT LOCAL; CREATE TABLESPACE ts_myscott2 LOGGING DATAFILE

4.windows和Linux下创建oracle用户名表空间,表,插入数据,用户管理表等操作

进入超级管理员,执行以下命令 Window下创建数据库,表空间,用户,插入数据等操作 -- 01 创建表空间 -- 注意表空间的路径 根据实际安装环境进行调整 CREATE TABLESPACE ts_myscott LOGGING DATAFILE 'F:/app/to-to/oradata/orcl/ts_myscott.dbf' SIZE 10M EXTENT MANAGEMENT LOCAL; CREATE TABLESPACE ts_myscott2 LOGGING DATAFILE

linux下创建用户

linux下创建用户(一) Linux 系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统.用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问:另一方面也可以帮助用户组织文件,并为用户提供安全性保护.每个用户账号都拥有一个惟一的用户名和各自的口令.用户在登录时键入正确的用户名和口令后,就能够进入系统和自己的主目录. 实现用户账号的管理,要完成的工作主要有如下几个方面: · 用户账

linux下创建svn仓库及用户

1 Linux下创建svn仓库 1.1 启动SVN服务 svnserve -d -r  /SVNRootDirectry 其中SVNRootDirectry是你的SVN 根目录,例如192.85.1.2上的是:/SVN.-d 表示以后太服务方式执行,-r就表示root 1.2 创建仓储 1.2.1 在SVN根目录下建立版本仓库 svnadmin create study 命令 :svnadmin create  msm 其中msm是版本仓库的名字,仓库建立好了以后会有出现一个仓库名字的文件夹,文

Linux下创建和删除用户

在Linux下创建用户和删除用户,必须在root用户下,如果你当前不是用根用户登录,你可以打开终端,输入"su root"命令,再输入根口令,就可以进入root用户模式下,如下所示: 创建用户(useradd): (1)用useradd命令创建用户创建用户: 语法: useradd [所要创建的用户名] ,回车 (2)用passwd命令为该用户创建密码: 语法: passwd [用户名]  ,回车 (3)输入密码:一般密码至少要有六个字符,这里输入的密码是看不见的,所以看到屏幕没显示,

LINUX下创建RAID1

实验 添加两块30G硬盘 在不重新启动的情况下,让Linux系统识别到新添加的磁盘 使用putty连接linux操作服务器 [[email protected] ~]# fdisk –l 没有识别到新添加的两块磁盘 [[email protected] ~]# echo "- - -" >/sys/class/scsi_host/host0/scan [[email protected] ~]# echo "-- -" > /sys/class/scsi

linux下创建可引导的U盘系统,使用dd命令进行Linux的ghost

1,通过iso创建可引导的U盘系统. 1.0,格式化U盘为FAT32格式 linux下能够使用命令: mkfs.vfat U盘的设备路径 比如: mkfs.vfat /dev/sdb 当中U盘的路径能够通过命令df来查看(df -h) 扩展:mkfs命令使用方法:以特定文件系统格式化分区,文件系统通过直接在命令后面加点和文件系统名来指定(命令行下能够通过双击Tab键来查看当前系统都支持创建那些类型的文件系统) mkfs.vfat ->创建fat32分区 mkfs.ntfs ->创建ntfs分区

linux下创建链接文件

在linux下创建链接文件 ln -s /home/{user}/Document/notepad.txt /home/{user}/Desktop/myNotepad 这样将在用户桌面创建一个myNotepad的链接文件(熟称快捷方式)用于指向用户的Document文件夹下的notepad.txt文件.