资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,第四章 Linux平台上的软件开发,本章概要,程序开发过程,代码生成工具,多模块软件的编译和链接,源代码调试,库的开发、调试和使用,RCS和CVS的版本控制,10/24/2026,1,4.5 库的开发、调试和使用,4.5.1 库的使用,4.5.2 ar实用程序,10/24/2026,2,简介,在Linux和UNIX操作系统中,我们可以将多个目标文件归档为一个单个库文件。,库的作用:,不再需要使用编译命令中或makefile中大量目标文件的名称,只使用一个库文件名称;,可以在很多程序中使用同一个函数。,10/24/2026,3,函数库,Linux和UNIX系统有许多针对C、C+和FORTRAN的预先打包的库。,这些库包含,许多函数的目标模块,,用以执行常用的操作。如:,I/O,字符处理,字符串处理,数学函数,等。,10/24/2026,4,库函数的位置,这些库函数在Linux或UNIX的/lib和/usr/lib目录中。如:,标准C库函数/usr/lib/libc.a,标准C+库函数/usr/lib/libstdc+.a,*.a是目标文件的库函数。,10/24/2026,6,为什么要使用二进制库,1)隐藏源代码的需要。,放眼现在市面上各个中间件软件提供商,所提供的产品绝大多数都是以二进制库的形式发布的。流行的比如Linux下的Qt图形库,Windows下微软的DirectX图形接口、.NET基类,以及嵌入式平台上的MiniGUI等等。,2)模块化、层次化设计的需要。,一 旦某个软件模块设计完成,可以将其打包成一个独立的库文件。这样系统其他部分就可以按照其提供的接口进行调用,而无须考虑代码层次的兼容性。其次,库文件 是已经编译好的二进制文件,被调用的库只需要静态或动态地链接到主调代码中即可,而无需再次编译库的源代码,大大提高了编译的速度和效率。,10/24/2026,7,4.5.1 库的使用,库的头文件包含库中定义的资源声明,如函数和宏。,有两种类型的头文件:,公有头文件,:其中声明的资源对于其他的程序也有效;,私有头文件,:其中声明的资源只能被库代码使用。,10/24/2026,8,使用标准库函数,通过加入对应于所使用库函数的头文件,就可以使用标准语言库中预先写好的函数。,例如:,#include,上面的指令在程序中加入头文件/usr/include/stdio.h,我们就可以在程序中直接使用标准C库函数printf()。,/usr/lib/libc.a是标准C语言库,可以自动被gcc编译程序链接,。,10/24/2026,10,非标准库函数,许多C/C+库函数可以在程序中使用,但它们不是标准的库函数。如:数学函数就不是C/C+标准库的组成部分。它们是由数学库(/usr/lib/libm.a)所定义的。,当我们使用该库的数学函数时,除了,#include ,之外,还必须明确地用,编译程序选项-lm链接,这个库。,10/24/2026,11,例题,$cat power.c,#include,#include,main(),float x,y;,printf(“the program takes x and y from stdin n”);,printf(“Enter number x:”);,scanf(“%f”,printf(“Enter number y:”);,scanf(“%f”,printf(“xy is%6.3fn”,pow(double)x,(double)y);,10/24/2026,13,常见的与库有关的编译错误,没有为库加入头文件;,没有明确地链接一个库,这个库不能被编译程序自动链接;,使用了错误版本的库;,链接了不兼容的库。,10/24/2026,15,C程序的构成,对于一个C程序来说,安装完成后通常可以分成三个组成:,1.可执行文件,2.包含文件,3.库文件,10/24/2026,16,静态库和动态库,我们通常把一些公用函数制作成函数库,供其它程序使用。,函数库分为静态库和动态库两种。,静态库,在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。,动态库,在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在。,10/24/2026,18,WINDOWS的动态链接库,在WINDOWS系统中有很多的动态链接库(以.DLL为后缀的文件,DLL即Dynamic Link Library)。,这种动态链接库,和静态函数库不同,它里面的函数并不是执行程序本身的一部分,而是根据执行程序需要按需装入,同时其执行代码可在多个执行程序间共享,节省了空间,提高了效率,具备很高的灵活性。,10/24/2026,19,LINUX的共享库,LINUX不仅有动态链接库,而且为数不少。,在/lib目录下,就有许多以.so作后缀的文件,,这就是LINUX系统应用的动态链接库,只不过与WINDOWS叫法不同,,它叫so,即Shared Object,共享对象,。,X-WINDOW作为LINUX下的标准图形窗口界面,它本身就采用了很多的动态链接库(在/usr/X11R6/lib目录下),以方便程序间的共享,节省占用空间。,著名的APACHE网页服务器,也采用了动态链接库,以便扩充程序功能。,10/24/2026,20,创建静态库和动态库,下面通过举例来说明在Linux中如何创建静态库和动态库,以及使用它们。,在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o文件。,第1步:编辑举例用的程序-hello.h、hello.c和main.c;,10/24/2026,21,程序1:hello.h,#ifndef HELLO_H,#define HELLO_H,void hello(const char*name);,#endif/HELLO_H,10/24/2026,22,程序3:main.c,#include hello.h,int main(),hello(everyone);return 0;,10/24/2026,24,三个文件,hello.c(见程序2)是,函数库的源程序,,其中包含公用函数hello,该函数将在屏幕上输出“Hello XXX!”。,hello.h(见程序1)为,该函数库的头文件,。,main.c(见程序3)为,测试库文件的主程序,,在主程序中调用了公用函数hello。,10/24/2026,25,第2步:将hello.c编译成.o文件;,无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件。,在系统提示符下键入以下命令得到hello.o文件。,$,g,cc-c hello.c,$,-c选项:只编译并生成目标文件。,10/24/2026,26,第3步:由.o文件创建静态库;,静态库文件名的命名规范是以,lib为前缀,,,紧接着跟静态库名,扩展名为.a,。,例如:我们将创建的静态库名为myhello,则静态库文件名就是,libmyhello.a,。,创建静态库用,ar命令,。,10/24/2026,28,在系统提示符下键入以下命令将创建静态库文件 libmyhello.a。,$,ar cr libmyhello.a hello.o,$,我们同样运行ls命令查看结果:,$,ls,hello.c hello.h hello.o,libmyhello.a,main.c,$,ls命令结果中有libmyhello.a。,r:在库中插入模块(替换)c:创建一个库。,10/24/2026,29,在程序3:main.c中,我们包含了静态库的头文件hello.h,然后在主程序main中直接调用公用函数hello。,下面先生成目标程序hello,然后运行hello程序看看结果如何。,$,gcc-o hello main.c L.-lmyhello,$,./hello,Hello everyone!,$,10/24/2026,31,我们删除静态库文件试试公用函数hello是否真的连接到目标文件 hello中了。,$,rm libmyhello.a,rm:remove regular file libmyhello.a?,y,$,./hello,Hello everyone!,$,程序照常运行,静态库中的公用函数已经连接到目标文件中了。,10/24/2026,32,创建动态库,我们继续看看如何在Linux中创建动态库。我们还是从.o文件开始。,第5步:由.o文件创建动态库文件;,动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其,文件扩展名为.so,。,例如:我们将创建的动态库名为myhello,则动态库文件名就是,libmyhello.so,。用,gcc,来创建动态库。,10/24/2026,33,用编译命令创建动态链接库,在LINUX系统下,创建动态链接库只要,在编译函数库源程序时加上-shared选项即可,,这样所生成的执行程序即为动态链接库。,从某种意义上来说,动态链接库也是一种执行程序。按一般规则,程序名应,带.so后缀,。,10/24/2026,34,在系统提示符下键入以下命令得到动态库文件libmyhello.so。,$,g,cc,-shared,-o libmyhello.so hello.o,$,-shared生成共享目标文件。通常用在建立共享库时。,10/24/2026,35,我们照样使用ls命令看看动态库文件是否生成。,$,ls,hello.c hello.h hello.o,libmyhello.so,main.c,$,10/24/2026,36,第6步:在程序中使用动态库,;,在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。,我们先运行gcc命令生成目标文件,再运行它看看结果。,$,gcc-o hello main.c-L-lmyhello,$,./hello,10/24/2026,37,./hello:error while loading shared libraries:libmyhello.so:cannot open shared object file:No such directory,出错了!错误提示表明原来是找不到动态库文件libmyhello.so。,程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。,我们,将文件libmyhello.so复制到目录/usr/lib中,,再试试。,10/24/2026,38,$,mv libmyhello.so/usr/lib,$,./hello,Hello everyone!,$,成功了。这也进一步说明了动态库在程序运行时是需要的。,10/24/2026,39,我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样,,$,gcc-o hello main.c L.lmyhello,$,gcc-o hello main.c L.-lmyhello,那,当静态库和动态库同名时,,,gcc命令会使用哪个库文件呢?,10/24/2026,40,先删除除.c和.h外的所有文件,恢复成我们刚刚编辑完举例程序状态。,$rm-f hello hello.o/usr/lib/libmyhello.so,$ls,hello.c hello.h main.c,$,再来创建静态库文件libmyhello.a和动态库文件libmyhello.so。,10/24/2026,41,$,gcc-c hello.c,$,ar cr libmyhello.a hello.o,$,gcc-shared-fPCI-o libmyhello.so hello.o,$,ls,hello.c hello.h hello.o,libmyhello.a libmyhello.so,main.c,$,通过上述最后一条ls命令,可以发现静态库文件libmyhello.a和动态库文件libmyhello.so都已经生成,并都在当前目录中。,然后,我们,运行gcc命令来使用函数库myhello生成目标文件hello,并运行程序 hello,。,10/24/2026,42,$,gcc-o hello main.c-L.-lmyhello,$,./hello,./hello:error while loading shared libraries:,libmyhello.so,:cannot open shared object file:No such directory,$,从程序hello运行的结果中很容易知道,当静态库和动态库同名时,,gcc命令将优先使用动态库,。,10/24/2026,43,GCC编译参数解析,-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件.,-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。,-L.:表示要连接的库在当前目录中,-lmyhello:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称。,10/24/2026,44,GCC编译参数解析,LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。,当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用/sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。,10/24/2026,45,调用动态库易遇到的问题,调用动态库的时候有几个问题会经常碰到。,已经将库的头文件所在目录 通过“-l”include进来了,库所在文件通过“-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是找不到指定链接的so文件。,这时要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。,10/24/2026,46,4.5.2 ar实用程序,当我们的程序中有经常使用的模块,而且这种模块在其他程序中也会用到,这时按照软件重用的思想,我们应该将它们生成,库,,使得以后编程能够减少研发代码量。,下面介绍两个命令ar和nm,用来对库操作。,10/24/2026,47,ar的基本用法,ar命令能够用来,创建、修改库,,也能够从库中,提出单个模块,。,库是一单独的文档,,里面包含了按照特定的结构组织起来的其他的一些文档(称做此库文档的member)。原始文档的内容、模式、时间戳、属主、组等属性都保留在库文档中。,10/24/2026,48,ar命令格式,ar 被当成一个二进制工具,因为由 ar 生成的归档文件一般被用了当成静态库使用。,下面是ar命令的格式:,ar-dmpqrtxabcfilNoPsSuvV membername count archive files.,10/24/2026,49,例,例如:,ar rv lib test.a hello.o hello1.o,生成一个库,库名字是test,链接时能够用-ltest链接。,该库中存放了两个模块hello.o和hello1.o。,10/24/2026,50,ar命令的操作选项和任选项,我们把dmpqrtx部分称为操作选项,而abcfilNoPsSuvV部分称为任选项。,dmpqrtx中的操作选项在命令中只能并且必须使用其中一个,它们的含义如下。,d:,从库中删除模块,。按模块原来的文档名指定要删除的模块。假如使用了任选项v则列出被删除的每个模块。,10/24/2026,51,dmpqrtx中的操作选项,m:,该操作是在一个库中移动成员,。当库中假如有若干模块有相同的符号定义(如函数定义),则成员的位置顺序很重要。,假如没有指定任选项,任何指定的成员将移到库的最后。也能够使用a,b,或I任选项移动到指定的位置。,10/24/2026,52,dmpqrtx中的操作选项,p:,显示库中指定的成员到标准输出,。假如指定任选项v,则在输出成员的内容前,将显示成员的名字。假如没有指定成员的名字,任何库中的文档将显示出来。,q:快速追加。,增加新模块到库的结尾处,。并不检查是否需要替换。a,b,或I任选项对此操作没有影响,模块总是追加的库的结尾处。,10/24/2026,53,dmpqrtx中的操作选项,r:,在库中插入模块(替换),。当插入的模块名已在库中存在,则替换同名的模块。假如若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,能够使用其他任选项来改变增加的位置。,t:,显示库的模块表清单,。一般只显示模块名。,x:,从库中提取一个成员,。假如不指定要提取的模块,则提取库中任何的模块。,10/24/2026,54,abcfilNoPsSuvV 任选项,可和操作选项结合使用的任选项:,a:在库的一个已存在的成员后面增加一个新的文档。假如使用任选项a,则应该为命令行中membername参数指定一个已存在的成员名。,b:在库的一个已存在的成员前面增加一个新的文档。假如使用任选项b,则应该为命令行中membername参数指定一个已存在的成员名。,c:创建一个库。不管库是否存在,都将创建。,10/24/2026,55,abcfilNoPsSuvV 任选项,f:在库中截短指定的名字。缺省情况下,文档名的长度是不受限制的,能够使用此参数将文档名截短,以确保和其他系统的兼容。,i:在库的一个已存在的成员前面增加一个新的文档。假如使用任选项i,则应该为命令行中membername参数指定一个已存在的成员名(类似任选项b)。,l:暂未使用,N:和count参数一起使用,在库中有多个相同的文档名时指定提取或输出的个数。,10/24/2026,56,abcfilNoPsSuvV 任选项,o:当提取成员时,保留成员的原始数据。假如不指定该任选项,则提取出的模块的时间将标为提取出的时间。,P:进行文档名匹配时使用全路径名。ar在创建库时不能使用全路径名(这样的库文档不符合POSIX标准),但是有些工具能够。,s:写入一个目标文档索引到库中,或更新一个存在的目标文档索引。甚至对于没有任何变化的库也作该动作。对一个库做ar s等同于对该库做ranlib。,10/24/2026,57,abcfilNoPsSuvV 任选项,S:不创建目标文档索引,这在创建较大的库时能加快时间。,u:一般说来,命令ar r.插入任何列出的文档到库中,假如您只想插入列出文档中那些比库中同名文档新的文档,就能够使用该任选项。该任选项只用于r操作选项。,v:该选项用来显示执行操作选项的附加信息。,V:显示ar的版本。,10/24/2026,58,排序归档,归档文件中的目标文件不是以任意顺序排列的,其排列顺序依赖于模块被插入到归档中的顺序。,ranlib命令用于将目录添加到一个或多个归档中。这些归档可以作为ranlib的参数。,ranlib命令的功能与带有-s的ar命令功能相同。,带有目录的归档在链接阶段是必要的,它允许归档中的函数互相调用,无论这些函数在归档中的位置如何。,10/24/2026,59,ranlib命令格式,ranlib,-t,-X32|64|32_64,Archive.,例如:,要随机化归档文件 genlib.a,请输入:,ranlib genlib.a,10/24/2026,60,命令选项,-t,提到指定的归档而不修改它们。,-X,mode,指定,ranlib,要检查的目标文件的类型。,mode,必须是下列之一:,32,仅仅处理 32 位的目标文件,64,仅仅处理 64 位的目标文件,32_64,处理 32 位和 64 位的目标文件,10/24/2026,61,nm实用程序,nm用来显示一个库、目标或可执行文件的符号表(即名称、类型、大小和进入点)。,nm所显示的信息用于调试库和可执行文件。,10/24/2026,62,nm命令格式,nm用来列出目标文档的符号清单。下面是nm命令的格式:nm-a|-debug-syms-g|-extern-only-B-C|-demangle-D|-dynamic-s|-print-armap-o|-print-n|-numeric-sort-p|-no-sort-r|-reverse-sort-size-sort-u|-undefined-only-l|-line-numbers-help-version-t radix|-radix=radix-P|-portability-f format|-format=format-target=bfdname objfile.,10/24/2026,63,nm命令用法,假如没有为nm命令指出目标文档,则nm假定目标文档是a.out。,下面列出该命令的任选项,大部分支持-开头的短格式和开头的长格式。,10/24/2026,64,-A、-o或-print-:在找到的各个符号的名字前加上文档名,而不是在此文档的任何符号前只出现文档名一次。,例如,nm libtest.a,的输出如下:,CPThread.o:00000068 T Main_8CPThreadPv00000038 T Start_8CPThread00000014 T _._8CPThread00000000 T _8CPThread00000000?_FRAME_BEGIN_,10/24/2026,65,则,nm-A libtest.a,的输出如下:,libtest.a,:CPThread.o:00000068 T Main_8CPThreadPv,libtest.a,:CPThread.o:00000038 T Start_8CPThread,libtest.a,:CPThread.o:00000014 T _._8CPThread,libtest.a,:CPThread.o:00000000 T _8CPThread,libtest.a,:CPThread.o:00000000?_FRAME_BEGIN_,10/24/2026,66,命令选项,-a或-debug-syms:显示调试符号。,-B:等同于-format=bsd,用来兼容MIPS的nm。,-C或-demangle:将低级符号名解码(demangle)成用户级名字。这样能够使得C+函数名具备可读性。,-D或-dynamic:显示动态符号,。该任选项仅对于动态目标(例如特定类型的共享库)有意义。,10/24/2026,67,命令选项,-f format:使用format格式输出。format能够选取bsd、sysv或posix,该选项在GNU的nm中有用。默认为bsd。,-g或-extern-only:仅显示外部(全局)符号。,-n、-v或-numeric-sort:按符号对应地址的顺序排序,而非按符号名的字符顺序。,-p或-no-sort:按目标文档中碰到的符号顺序显示,不排序。,-P或-portability:使用POSIX.2标准输出格式代替默认的输出格式。等同于使用任选项-f posix。,10/24/2026,68,命令选项,-s或-print-armap:当列出库中成员的符号时,包含索引。索引的内容包含:哪些模块包含哪些名字的映射。,-r或-reverse-sort:反转排序的顺序(例如,升序变为降序)。,-size-sort:按大小排列符号顺序。该大小是按照一个符号的值和他下一个符号的值进行计算的。,-t radix或-radix=radix:使用radix进制显示符号值。radix只能为d表示十进制、o表示八进制或x表示十六进制。,10/24/2026,69,命令选项,-target=bfdname:指定一个目标代码的格式,而非使用系统的默认格式。,-u或-undefined-only:仅显示没有定义的符号(那些外部符号)。,-l或-line-numbers:对每个符号,使用调试信息来试图找到文档名和行号。对于已定义的符号,查找符号地址的行号。对于未定义符号,查找指向符号重定位入口的行号。假如能够找到行号信息,显示在符号信息之后。,-V或-version:显示nm的版本号,。,-help:显示nm的任选项。,10/24/2026,70,nm的输出信息,nm实用程序至少会将下面的信息输出至标准输出:,每个符号的地址;,符号的值;,符号的类型。,符号的类型使用字母指示。大写字母代表全局符号,小写字母代表局部符号。,10/24/2026,71,Linux二进制映像,在Linux中程序的二进制映像包括四个方面:,文本/代码;,数据;,栈;,堆。,文本部分包括程序代码,数据部分包括数据变量(初始化的和未初始化的)。,10/24/2026,72,二进制映像,文本部分包括程序代码;,数据部分包括数据变量(初始化的和未初始化的);,栈部分包括用于函数调用的活动记录;,堆部分包括动态分配和释放(通过调用函数malloc和free申请和释放的内存空间C;通过使用操作符new和delete申请和释放的内存空间C+)。,10/24/2026,73,nm实用程序输出中的类型字母,类型字母,意义,A,全局绝对符号,B,未初始化数据段中的全局变量(bss),D,初始化数据段中的符号,F,文件符号,T,有关程序的文本(代码)部分的全局符号,这些符号多是函数名,U,未定义符号,一个外部符号(通常用在库函数中),10/24/2026,74,例,nm libtest.a,CPThread.o:00000068 T Main_8CPThreadPv00000038 T Start_8CPThread00000014 T _._8CPThread00000000 T _8CPThread00000000?_FRAME_BEGIN_,10/24/2026,75,
展开阅读全文