本文通过如何调用库文件来说述gcc程序编译
甚么是库
在windows平台和linux平台下都大量存在着库。本质上来讲库是1种可履行代码的2进制情势,可以被操作系统载入内存履行。由于windows和linux的平台不同(主要是编译器、汇编器和连接器的不同),因此2者库的2进制是不兼容的。
本文仅限于介绍linux下的库。
库的种类
linux下的库有两种:静态库和同享库(动态库)。2者的不同点在于代码被载入的时刻不同。静态库的代码在编译进程中已被载入可履行程序,因此体积较大。同享库的代码是在可履行程序运行时才载入内存的,在编译进程中仅简单的援用,因此代码体积较小。
库存在的意义
库是他人写好的现有的,成熟的,可以复用的代码,你可使用但要记得遵照许可协议。现实中每一个程序都要依赖很多基础的底层库,不可能每一个人的代码都从零开始,因此库的存在乎义非同寻常。
库文件是如何产生的在linux下
静态库的后缀是.a,它的产生分两步
1. 由源文件编译生成1堆.o,每一个.o里都包括这个编译单元的符表
2. ar命令将很多.o转换成.a,成为静态库动态库的后缀是.so,它由gcc加特定参数编译产生。
具体方法参见后文实例。
库文件是如何命名的,有无甚么规范
在linux下,库文件1般放在/usr/lib和/lib下,静态库的名字1般为libxxxx.a,其中xxxx是该lib的名称。动态库的名字1般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号
如何知道1个可履行程序依赖哪些库
ldd命令可以查看1个可履行程序依赖的同享库,
比如查看ln程序的依赖库$ ldd /bin/ls
$ ldd /bin/ls
linux-vdso.so.1 => (0x00007ffd22c47000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007ff5f2a6f000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007ff5f2867000)
libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x00007ff5f265f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff5f22a1000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff5f209d000)
/lib64/ld-linux-x86⑹4.so.2 (0x00007ff5f2c8e000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff5f1e80000)
libattr.so.1=>/lib/x86_64-linux-gnu/libattr.so.1(0x00007ff5f1c7b000)
可以看到ls命令依赖于librt.so.1库,libc.so.6库。。。
我们通常把1些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时其实不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
本文主要通过举例来讲明在Linux中如何创建静态库和动态库,和使用它们。为了便于论述,我们先做1部份准备工作。
准备好测试代码hello.h、hello.c和main.c;
hello.h(见程序1)为该函数库的头文件。
hello.c(见程序2)是函数库的源程序,其中包括公用函数hello,该函数将在屏幕上输出”HelloXXX!”。
main.c(见程序3)为测试库文件的主程序,在主程序中调用了公用函数hello。
程序1:hello.h
#ifndefHELLO_H
#defineHELLO_H
void hello(constchar*name);
#endif
程序2:hello.c
#include<stdio.h>
void hello(constchar*name){
printf(“Hello%s!\n”,name);
}
程序3:main.c
#include“hello.h”
int main()
{
hello(“everyone”);
return0;
}
问题的提出
注意:这个时候,我们编译好的hello.o是没法通过gcc -o编译的,这个道理非常简单,hello.c是1个没有main函数的.c程序,因此不够成1个完全的程序,如果使用gcc -o编译并连接它,GCC将报错。编译main.c也会出错,由于找不到hello的函数原型及实现。不管静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件。
这个时候我们有3种思路:
1. 通过编译多个源文件,直接将目标代码合成1个.o文件。
2. 通过创建静态连接库libmyhello.a,使得main函数调用hello函数时可调用静态连接库.
3. 通过创建动态连接库libmyhello.so,使得main函数调用hello函数时可调用静态连接库。
思路1:编译多个源文件
在系统提示符下键入以下命令得到hello.o文件。
$ gcc-chello.c
为何不使用gcc -o hello hello.cpp
这个道理我们之前已说了,使用-c是甚么意思呢?
这触及到gcc编译选项的常识。我们通常使用的gcc –o是将.c源文件编译成为1个可履行的2进制代码(-o选项实际上是制定输出文件文件名,如果不加-c选项,gcc默许会编译连接生成可履行文件,文件的名称有-o选项指定),这包括调用作为GCC内的1部份真实的C编译器(ccl),和调用GNU C编译器的输出中实际可履行代码的外部GNU汇编器(as)和连接器工具(ld)。而gcc -c是使用GNU汇编器将源文件转化为目标代码以后就结束,在这类情况下,只调用了C编译器(ccl)和汇编器(as),而连接器(ld)并没有被履行,所以输出的目标文件不会包括作为Linux程序在被装载和履行时所必须的包括信息,但它可以在以后被连接到1个程序。
我们运行ls命令看看是不是生成了hello.o文件。
$ ls
hello.c hello.h hello.o main.c
在ls命令结果中,我们看到了hello.o文件,本步操作完成。
同理编译main
$ gcc–cmain.c
将两个文件连接成1个.o文件。
$gcc –o hello hello.o main.o
运行
$./hello
Hello everyone!
完成^^
思路2:静态连接库
下面我们先来看看如何创建静态库,和使用它。静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩大名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用ar命令。在系统提示符下键入以下命令将创建静态库文件libmyhello.a。
$ ar rcs libmyhello.a hello.o
我们一样运行ls命令查看结果:
$ls
hello.c hello.h hello.o libmyhello.a main.c
ls命令结果中有libmyhello.a。
静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包括这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩大名.a得到的静态库文件名来查找静态库文件,因此,我们在写需要连接的库时,只写名字就能够,如libmyhello.a的库,只写-lmyhello。其实也能够直接在调用静态库的全名即libmyhello.a
在程序3:main.c中,我们包括了静态库的头文件hello.h,然后在主程序main中直接调用公用函数hello。下面生成目标程序hello,然后运行hello程序看看结果如何。
方式1:$ gcc -o hellomain.c –static –L ./ -lmyhello
方式2:$ gcc -o hellomain.c –static –L ./ -libmyhello.a
两种方式都可以生成已连接静态库的hello程序
$./hello
Hello everyone!
我们删除静态库文件试试公用函数hello是不是真的连接到目标文件hello中了。
$rm libmyhello.a
rm:remove regular file`libmyhello.a’? y
$./hello
Hello everyone!
程序照旧运行,静态库中的公用函数已连接到目标文件中了。
静态连接库的1个缺点是,如果我们同时运行了许多程序,并且它们使用了同1个库函数,这样,在内存中会大量拷贝同1库函数。这样,就会浪费很多珍贵的内存和存储空间。使用了同享连接库的Linux就能够避免这个问题。
同享函数库和静态函数在同1个地方,只是后缀有所不同。比如,在1个典型的Linux系统,标准的同享数序函数库是/usr/lib/libm.so。
当1个程序使用同享函数库时,在连接阶段其实不把函数代码连接进来,而只是连接函数的1个援用。当终究的函数导入内存开始真正履行时,函数援用被解析,同享函数库的代码才真正导入到内存中。这样,同享连接库的函数就能够被许多程序同时同享,并且只需存储1次就能够了。同享函数库的另外一个优点是,它可以独立更新,与调用它的函数绝不影响。
思路3、动态连接库(同享函数库)
我们继续看看如何在Linux中创建动态库。我们还是从.o文件开始。动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩大名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。
在系统提示符下键入以下命令得到动态库文件libmyhello.so。
$ gcc -c -fPIC -o hello.o hello.c
$ gcc –shared –o libmyhello.so hello.o
“PIC”命令行标记告知GCC产生的代码不要包括对函数和变量具体内存位置的援用,这是由于现在还没法知道使用该消息代码的利用程序会将它连接到哪1段内存地址空间。这样编译出的hello.o可以被用于建立同享连接库。建立同享连接库只需要用GCC的”-shared”标记便可。
我们照样使用ls命令看看动态库文件是不是生成。
$ls
hello.cpphello.hhello.olibmyhello.somain.cpp
在程序中使用动态库和使用静态库完全1样,也是在使用到这些公用函数的源程序中包括这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。我们先运行gcc命令生成目标文件,再运行它看看结果。
如果直接用以下方法进行编译,并连接:
$ gcc –o hello main.c –L ./ -lmyhello
(使用”-lmyhello”标记来告知GCC驱动程序在连接阶段援用同享函数库libmyhello.so。“-L ./”标记告知GCC函数库可能位于当前目录。否则GNU连接器会查找标准系统函数目录:它前后搜索
1. elf文件的DT_RPATH段
2. 环境变量LD_LIBRARY_PATH
3. /etc/ld.so.cache文件列表
4. /lib/
5. /usr/lib目录
找到库文件后将其载入内存,但是我们生成的同享库在当前文件夹下,并没有加到上述的4个路径的任何1个中,因此,履行后会出现毛病)
$./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
毛病提示,找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述毛病而终止程序运行。有多种方法可以解决,
我们将文件libmyhello.so复制到目录/usr/lib中,再试试。
$mv libmyhello.so /usr/lib
$./hello
成功!
既然连接器会搜索LD_LIBRARY_PATH所指定的目录,那末我们可以将这个环境变量设置成当前目录:
先履行:
$exportLD_LIBRARY_PATH=$(pwd)
再履行:
$./hello
成功!
履行:
sudo ldconfig /usr/local/lib
注:当用户在某个目录下面创建或拷贝了1个动态连接库,若想使其被系统同享,可以履行1下”ldconfig目录名“这个命令.此命令的功能在于让ldconfig将指定目录下的动态连接库被系统同享起来,意即:在缓存文件/etc/ld.so.cache中追加进指定目录下的同享库.本例让系统同享了/usr/local/lib目录下的动态连接库.该命令会重建/etc/ld.so.cache文件
成功!
这也进1步说明了动态库在程序运行时是需要的。
可以查看程序履行时调用动态库的进程:
$ld dhello
履行test,可以看到它是如何调用动态库中的函数的。
$lddhello
linux-gate.so.1=>(0x00110000)
libmyhello.so=>/usr/lib/libmyhello.so(0x00111000)
libc.so.6=>/lib/libc.so.6(0x00859000)
/lib/ld-linux.so.2(0x0083a000)
ok,程序调用动态库的功能完成。
C和C++编译器是集成的,它们都需要1个或多个处理输入文件:预处理(perprocessing),编译(compilation),汇编(assembly)和连接(linking)。
整体选项
-c
:编译或汇编文件,但是不做连接。缺省情况下,GCC通过使用‘.o’来替换源文件名后缀‘.c’,‘.i’,‘.s’等等
-o file
: 指定输出的可履行文件为file且只能输出1个可履行文件,如果没有使用‘-o’选项及命令为gcc xx.c。
连接器选项
-shared
:生成1个同享目标文件,它可以和其它目标文件连接产生可履行文件。
-llibrary
: 连接名为library的库文件。连接器会在标准搜索目录中寻觅这个库文件。搜索目录除1些标准的搜索目录外还包括用户使用‘-L’选项指定的路径
目录选项
-Ldir
:在‘-L’选项的搜索连接列表主功能添加dir目录
LD_LIBRARY_PATH
:这个环境变量唆使动态连接器可以装载动态库的路径。
代码生成选项:
-fPIC
:表示编译为位置独立的代码(适用于同享库),不用此选项的话编译后的代码是位置相干的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能到达真正代码段同享的目的。
正告选项
-Wall
:显示大部份常见的正告,如:定义了变量没有使用,变量未初始化等等
调试选项
-g
:以操作系统的本地格式产生调试信息,GDB能够使用这些调试信息,进行程序调试
参考文章:Linux下Gcc生成和使用静态库和动态库详解(转)