一.库的定义

什么是库,在windows平台和linux平台下都大量存在着库。

本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。

由于windows和linux的本质不同,因此二者库的二进制是不兼容的。

本文仅限于介绍linux下的库。

二.库的种类

1. 在windows中

.dll 动态库

.lib 静态库

库即为源代码的二进制文件

2. 在linux中

.so 动态库

.a      静态库

二者的不同点在于代码被载入的时刻不同。

静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。

共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。

三.库存在的意义

库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。

现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

四.库文件是如何产生的在linux下

静态库的后缀是.a,它的产生分两步:

Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表

Step 2.ar命令将很多.o转换成.a,成文静态库

动态库的后缀是.so,它由gcc加特定参数编译产生。

例如:

$ gcc -fPIC -c *.c $ gcc -shared -Wl,-soname, libfoo.so.1 -olibfoo.so.1.0 *.

五.库文件是如何命名的,有没有什么规范

在linux下,库文件一般放在/usr/lib和/lib下,

静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称

动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号

六.程序在不同环境下运行时怎么寻找库

1. Linux系统默认到哪里找命令,如果命令不在那里,该怎么设置?

(1) Linux在运行一条命令时,默认会到 /bin , /sbin ,, /usr/bin, /usr/sbin目录下去找;如果找不到则报command not found
(2)如果命令存放在其他路径下,我们可以通过export PATH导出,不过这样只是临时生效;
(3) 如果想让所有用户生效,则修改/etc/profile,里面通过export PATH导出路径;
(4) 在PC上如果只想对本用户生效,则修改~/.bash_profile

2. LInux程序在运行时到哪里找动态库, 如果动态库不在那里,该怎么设置?

(1)若是在开发板程序运行时:修改/etc/profile,export LD_LIBRARY_PATH添加库的其他存放路径。

(2)若是在片PC上程序运行时:动态库默认路径为/usr/lib和/lib,可在/etc/ld.so.conf中添加指定动态库搜索路径,通过LD_LIBRARY_PATH(LD_LIBRARY_PATH是Linux​环境变量​​名,该环境变量主要用于指定查找共享库(​​动态链接​​库)时除了默认路径之外的其他路径)命令指定,假如现在需要在已有的环境变量上添加新的路径名,则采用如下方式:

LD_LIBRARY_PATH=NEWDIRS:$LD_LIBRARY_PATH.(newdirs是新的路径串)。

注:在linux下可以用export命令来设置这个值,比如

在linux终端下输入:export LD_LIBRARY_PATH=/opt/au1200_rm/build_tools/bin: $LD_LIBRARY_PATH:

然后再输入:export

即会显示是否设置正确

export方式在重启后失效,所以也可以用 vim /etc/bashrc ,修改其中的LD_LIBRARY_PATH变量。

例如:LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/au1200_rm/build_tools/bin。

3.编译的时候:

编译器编译的时候,默认到哪里找头文件,如果不在怎么指定编译器到哪里找头文件;

链接器在做连接的时候,默认到哪里找动态库,如果不在怎么指定链接器到哪里找库文件;

链接器如果找不到某个函数或变量的定义,该怎么指定链接器要链接哪个库文件;

如果是交叉编译器的话,就不是在/usr/include下找了,而是在交叉编译器路径下找,同样,交叉编译的动态库也是默认到交叉编译器的路径下去找,如果头文件存放在其他路径,通过-I 指定,动态库存放在其他路径,则通过-L指定路口,-l指定库的名字。

七.如何知道一个可执行程序依赖哪些库

ldd命令可以查看一个可执行程序依赖的共享库

例如:

[leiyuxing@centos6 lib]$ ldd libxtables.so.4

linux-gate.so.1 =>  (0x007cd000)

libdl.so.2 => /lib/libdl.so.2 (0x00de3000)

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

/lib/ld-linux.so.2 (0x00bde000)

八.可执行程序在执行的时候如何定位共享库文件

当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径

此时就需要系统动态载入器(dynamiclinker/loader),对于elf格式的可执行程序,是由ld-linux.so*来完成的。

它先后搜索elf文件的DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目录找到库文件后将其载入内存。

九.库的创建

1.静态库

(1)定义:

这类库的名字一般是libname.a.利用静态库编写的文件比较大,原因是整个函数库中的数据都被整合进目标代码文件中去。它的优点是,编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进可执行文件了。同样它的不足,如果静态函数库改变了,那么你的程序必须重新编译,而且体积也较大。

(2)静态库的创建

在linux环境中,使用ar命令创建静态库文件.如下是命令的选项:

d -----从指定的静态库文件中删除文件

m -----把文件移动到指定的静态库文件中

p -----把静态库文件中指定的文件输出到标准输出

q -----快速地把文件追加到静态库文件中

r -----把文件插入到静态库文件中

t -----显示静态库文件中文件的列表

x -----从静态库文件中提取文件

还有多个修饰符修改以上基本选项,详细请man ar以下列出三个:

a -----把新的目标文件(*.o)添加到静态库文件中现有文件之后

b-----***************************************之前

v -----使用详细模式

ar 命令的命令行格式如下:

gcc -c filen.c

ar -cr libname.a file1.o file2.o

(3)创建库文件之后,可以创建这个静态库文件的索引来帮助提高和库连接的其他程序的编译速度:

使用ranlib程序创建库的索引,索引存放在库文件内部.

ranlib libapue.a

用nm程序显示存档文件的索引,它可以显示目标文件的符号

nm libapue.a | more

如果是显示目标文件的符号:

nm error.o | more

如何使用呢?如下所示:

gcc -o test test.c libapue.a

这样就可以在test.c中调用在libapue.a中的函数了.

 

2.动态库

(1)定义:

名字一般是libname.so.相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数是菜调用函数库里的函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。而且如果多个应用程序都要使用同一函数库,动态库就非常适合,可以减少应用程序的体积。

(2)动态库的创建

gcc -shard -fpic -o libname.so test1.c test2.c ....

-fpic:产生代码位置无关代码

-shared 生成一个共享库

(3)编译共享库

假设共享库位于当前目录(即跟程序文件相同的目录中)

gcc -o test -L. -lapue test.c

这样就编译出了不包含函数代码可执行文件了

十.实际操作

1.编写三个简单函数ADD.c SUB.c MUL.c

[leiyuxing@centos6 ~]$ mkdir src

[leiyuxing@centos6 ~]$ mkdir app

[leiyuxing@centos6 ~]$ cd src/

(1)ADD.c函数的编写

[leiyuxing@centos6 src]$ vim ADD.c

#include <stdio.h>
int ADD(int a, int b)
{
return a+b;
}


(2)SUB.c函数的编写

[leiyuxing@centos6 src]$ vim SUB.c

#include <stdio.h>
int SUB(int a ,int b)
{
return a-b;
}

(3)MUL.c函数的编写

[leiyuxing@centos6 src]$ vim MUL.c

#include <stdio.h>
#include <stdio.h>
int MUL(int a , int b)
{
return a*b;
}

(4)头文件fun.h的编写

[leiyuxing@centos6 src]$ vim fun.h

#ifdef __FUN_H_
#define __FUN_H_
extern ADD(int a , int b);
extern SUB(int a , int b);
extern MUL(int a , int b);
#endif

(5)main函数的编写

[leiyuxing@centos6 ~]$ cd app/

[leiyuxing@centos6 app]$ vim main.c

#include <stdio.h>

#include <fun.h>
int main (void)
{
int a=5;
int b=10;
printf("a=5,b=10\n");
printf("a+b=%d\n",ADD(a,b));
printf("a-b=%d\n",SUB(a,b));
printf("a*b=%d\n",MUL(a,b));
return 0;
}

2.生成静态库

[leiyuxing@centos6 ~]$ cd src/

[leiyuxing@centos6 src]$ ls

ADD.C  fun.h  MUL.c  SUB.c

[l[leiyuxing@centos6 src]$ gcc -c *.c

[leiyuxing@centos6 src]$ ls

ADD.c  ADD.o  fun.h  MUL.c  MUL.o  SUB.c  SUB.o

[leiyuxing@centos6 src]$ ar -cr libfun.a *.o

[leiyuxing@centos6 src]$ ls

ADD.c  ADD.o  fun.h  libfun.a

此时生成的libfun.a 即为一个静态库。

3.Makefile的编写及了解

在生成动态库前我们先了解一下Makefile 的编写,毕竟生成动态库还需依赖Makefile

(1)概述

Linux 环境下的程序员如果不会使用GNU make来构建和管理自己的工程,应该不能算是一个合格的专业程序员,至少不能称得上是Unix程序员。在Linux(unix )环境下使用GNU的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。不过这需要投入一些时间去完成一个或者多个称之为Makefile文件的编写。

所要完成的Makefile文件描述了整个工程的编译、连接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建那些库文件以及如何创建这些库文件、如何最后产生想要得可执行文件。尽管看起来可能是很复杂的事情,但是为工程编写Makefile的好处是能够使用一行命令来完成自动化编译,一旦提供一个(通常对于一个工程来说会是多个)正确的Makefile。编译整个工程你所要做的唯一的一件事就是在​shell​ 提示符下输入make命令。整个工程完全自动编译,极大提高了效率。

make是一个命令工具,它解释Makefile中的指令(应该说是规则)。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile有自己的书写格式、关键字、函数。像C语言有自己的格式、关键字和函数一样。而且在Makefile中可以使用系统shell所提供的任何命令来完成想要的工作。Makefile(在其它的系统上可能是另外的文件名)在绝大多数的IDE开发环境中都在使用,已经成为一种工程的编译方法

(2)Makefile规范

Make  执行总目标

Make clean 执行Makefile 中的clean目标

Make -C directory 到directory中执行make

Make clean -C directory  同上两命令

Make -f common_Makefile 通过-f 指定一个Makefile文件

Make var=value   给Makefie传一个参数其值为value

# 注释

VAR=XXX    定义变量VAR,强制赋值

VAR+=XXX 追加

VAR?=XXX   之前定义,若使用之前没有定义则应先定义


Target: depend1 depend2 . .          #依赖可以是文件(目录)或其他目标

(tab)  action1 action2            #动作那一行必须以TAB键打头

Depend1:

@(tab)Action1 action2    #@j键表示不打印该行动作信息

(3)根据机子的函数生成的*.0文件和静态库编写Makefile

[leiyuxing@centos6 src]$ vim Makefile

LIB_NAME?=fun

all:static_library shared_library

static_library:
gcc -c *.c;
ar -cr lib${LIB_NAME}.a *.o;

shared_library:
gcc -shared -fpic -o lib${LIB_NAME}.so *.c;

clean:
rm -rf *.o
rm -rf *.a *.so


再对main.c编写Makefile

[leiyuxing@centos6 app]$ vim Makefile

APP_NAME?=APP

all:lib_make
gcc -static -I../src main.c -L../src -lfun -o ${APP_NAME};

lib_make:
make -C ../src;

clean:
rm -rf ${APP_NAME}


4.生成动态库

[leiyuxing@centos6 ~]$ cd src/

[leiyuxing@centos6 src]$ ls

ADD.c  ADD.o  fun.h  libfun.a  Makefile  MUL.c  MUL.o  SUB.c  SUB.o

[leiyuxing@centos6 src]$ gcc -shared -fpic -o libfun.so  *.c  

[leiyuxing@centos6 src]$ ls

ADD.c  ADD.o  fun.h  libfun.a  libfun.so  Makefile  MUL.c  MUL.o  SUB.c  SUB.o

这里生成的libfun.so 即为一个动态库

5.静态库与动态库的使用

对main.c进行编译

[leiyuxing@centos6 app]$ gcc -I../src main.c -L../src -lfun -o app

[leiyuxing@centos6 app]$ ls

app  main.c  Makefile

此时生成一个可执行文件app

[leiyuxing@centos6 app]$ file app

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

这时我们可以发现 系统编译时默认使用的是动态链接  执行时我们需要指定动态库的位置

两种方法:

(1)将函数库移动到/lib 或者/usr/lib下 (需要root权限)

[leiyuxing@centos6 ~]$ cd src/

[leiyuxing@centos6 src]$ ls

ADD.c  ADD.o  fun.h  libfun.a  libfun.so  Makefile  MUL.c  MUL.o  SUB.c  SUB.o

[leiyuxing@centos6 src]$ sudo cp libfun.so /lib/

[leiyuxing@centos6 ~]$ cd app

[leiyuxing@centos6 app]$ ./app

a=5,b=10

a+b=15

a-b=-5

a*b=50

(2)修改一个环境变量 LD_LIBRARY_PATH

[leiyuxing@centos6 app]$ ls

app  main.c  Makefile

[leiyuxing@centos6 app]$ export LD_LIBRARY_PATH='/home/leiyuxing/src'

[leiyuxing@centos6 app]$ ./app

a=5,b=10

a+b=15

a-b=-5

a*b=50

采用静态链接方式  需要-static关键字

[leiyuxing@centos6 app]$ gcc -static -I../src main.c -o bpp -L../src -lfun

[leiyuxing@centos6 app]$ ls

 bpp  main.c  Makefile

[leiyuxing@centos6 app]$ ./bpp

a=5,b=10

a+b=15

a-b=-5

a*b=50

6.动态库与静态库的比较

(1)使用du命令分别查看两个可执行文件的大小

[leiyuxing@centos6 app]$ du app

8       app

[leiyuxing@centos6 app]$ du bpp

632     bpp

(2)看一个文件执行依赖的动态库

[leiyuxing@centos6 app]$ ldd app

        linux-gate.so.1 =>  (0x00ac5000)

        libfun.so => /home/leiyuxing/src/libfun.so (0x00c48000)

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

        /lib/ld-linux.so.2 (0x00bde000)

[leiyuxing@centos6 app]$ ldd bpp

不是动态可执行文件

综上所述:

当程序与静态库连接时,库中目标文件所含的所有将被程序使用的函数的机器码被copy到最终的可执行文件中。这就会导致最终生成的可执行代码量相对变多,相当于编译器将代码补充完整了,这样运行起来相对就快些。不过会有个缺点: 占用磁盘和内存空间. 静态库会被添加到和它连接的每个程序中, 而且这些程序运行时, 都会被加载到内存中. 无形中又多消耗了更多的内存空间.

与共享库连接的可执行文件只包含它需要的函数的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间,更进一步,操作系统使用虚拟内存,使得一份共享库驻留在内存中被多个程序使用,也同时节约了内存。不过由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些,总的来说静态库是牺牲了空间效率,换取了时间效率,共享库是牺牲了时间效率换取了空间效率,没有好与坏的区别,只看具体需要了。

遇到的问题:

问题一:

[leiyuxing@centos6 app]$ gcc main.c

main.c:3:17: 错误:fun.h:没有那个文件或目录

编译器报错 找不到头文件  需要指定头文件的位置 

解决方法:

-I选项 指定头文件的路径

[leiyuxing@centos6 app]$ gcc -I../src main.c

/tmp/ccUfqu2w.o: In function `main':

main.c:(.text+0x35): undefined reference to `ADD'

main.c:(.text+0x5a): undefined reference to `SUB'

main.c:(.text+0x7f): undefined reference to `MUL'

collect2: ld 返回 1

问题二:

[leiyuxing@centos6 app]$ gcc -I../src main.c

/tmp/ccUfqu2w.o: In function `main':

main.c:(.text+0x35): undefined reference to `ADD'

main.c:(.text+0x5a): undefined reference to `SUB'

main.c:(.text+0x7f): undefined reference to `MUL'

collect2: ld 返回 1

 链接器再一次报错  未能找库

解决方法:

-L选项来指定库的路径

-l选项来指定库的名字 (去掉lib和.a .so)剩下的部分  即-lfun

[leiyuxing@centos6 app]$ gcc -I../src main.c -L../src -lfun -o app

[leiyuxing@centos6 app]$ ls

app  main.c  Makefile

问题三:

[leiyuxing@centos6 app]$ ./app

./app: error while loading shared libraries: libfun.so: cannot open shared object file: No such file or directory

执行时出错  找不到动态库

解决方法:

方法一:将函数库移动到/lib 或者/usr/lib下 (需要root权限)

方法二:修改一个环境变量 LD_LIBRARY_PATH