1 代码样例

1. /**************************************************************************/  
2. /*add.c*/  
3. int add(int x, int y)  
4. {  
5. return x + y;  
6.   
7. return 0;  
8. }  
9. /*************************************************************************/  
10. 然后add.h代码为:  
11. /*add.h*/  
12. #ifndef _ADD_H_  
13. #define _ADD_H_  
14.   
15. int add(int, int);  
16.   
17. #endif  
18. /***************************************************************************/  
19. main函数代码:  
20. /*main.c*/  
21. #include <stdio.h>  
22.   
23. int main(void)  
24. {  
25. printf("2+3= %d\n", add(2,3));  
26. return 0;  
27. }  
28. /**********************************************************************************/


2静态库的编译和使用

2.1 静态库的编译流程

将add.c 单独的源文件编译成静态库libadd.a

1. gcc -c add.c  //生成 add.o  
2. ar crv libadd.a add.o // 生成静态库libadd.a


2.2 静态库的使用

1. gcc -o main main.c  -I. -L. -ladd  (注: -I -L -l的意思在文章后面)  
2. 也可以: gcc -o main main.c -I ./libadd.a (注:这里 ./libadd.a 可以是相对地址或者绝对地址 如:./lib/libadd.a , /home/lisi/lib/lib.a)


3 动态库的编译和使用

3.1 动态库的编译流程

1. gcc -fPIC -c add.c // 生成add.o  
2. gcc -shared -o libadd.so add.o // 或者使用 ar crv libadd.so add.o

上面可合并成一行: gcc -fPIC -shared -o libadd.so add.c

注: -fPIC 使输出的对象模块是按照可重定位地址方式生成的(即与位置无关).

-shared 指定把对应的源文件生成对应的动态链接库库文件libstr.so文件

3.2 动态库的使用

3.2.1 隐式调用

代码编写与静态库一样,不需要包含到处函数的头文件,若主函数是C++程序(即.cpp), 则需要在main.cpp中用extern "C"{} 包含被调用的函数(add.c)的头文件(这里需要包含头文件是与.cpp和.c混合编译有关,同静态\动态库无关),用g++或者用gcc(加上一个链接的参数 -lstdc++)编译.

1 )代码编写:与静态库一样

2 ) 编译main.c 生成可执行程序(动态库隐式调用的使用)

1. // 第一种方式  
2. gcc -o main main.c ./libadd.so  
3. // 第二种方式  
4. gcc -o main main.c -L. libadd.so


3.2.1.1 问题1

 ./main: error while loading shared libraries,cannot open shared object file: No such file or directory?

解决方案:

方法一: libadd.so放到/usr/lib 或 /lib 中去.

方法二: export LD_LIBRARY_PATH=$(pwd) 或者 export LD_LIBRARY_PATH=./ ,

              可写入环境变量来支持当前目录寻找动态链接库

方法三: 在/etc/ld.so.conf文件加入我们生成的库目录(只支持绝对路径),然后执行 #/sbin/ldconfig.

              关于 /etc/ld.so.conf 可以参看:ldd命令

1. // 第三种方式  
2. // 将libadd.so 拷贝到目录 /user/lib 或者 /lib 中,然后执行   
3. gcc -o main main.c libadd.so  //此时不需要指定搜索路径


3.2.1.2 问题2

gcc编译文件时出现undefined reference to 'xxxx'的错误?

这是链接错误,不是编译错误,源程序代码本身没有问题,是你的编译时参数用的不对.你没有指定连接程序要用到的库,比如你的程序里用到了一些数学函数sqrt ,那么你就要在编译参数里指定程序要链接数学库。

1. gcc -o math math.c -lm


3.2.2 显示调用

3.2.2.1 dlopen

在main.c 中增加头文件 #include <dlfcn.h>引入dlopen , dlsym , dlclose , dlerror 几个系统调用

主要介绍dlopen()系统调用

第一个参数:指定共享库的名称,将会在下面位置查找指定的共享库.

-环境变量 LD_LIBARARY_PATH列出的用冒号间隔的所有目录

-文件/etc/ld.so.cache重找到库的列表,用 ldconfig 维护

-目录/usr/lib

-目录/lib

-当前目录

第二个参数:指定打开共享库.

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

-RTLD_LAZY:会退后共享库中的函数的加载操作,知道调用dlsym()时方加载某函数

返回值:返回动态库的句柄

1. #include <stdio.h>  
2. #include <dlfcn.h>  
3.   
4. int main()  
5. {  
6. int (*func)(int , int );  
7. void *dl = dlopen("./libadd.so", RTLD_LAZY);  
8. if (dl == NULL)  
9. return -1;  
10. "add");  
11. if (func == NULL)  
12. return -1;  
13. "2+3=%d", func(2, 3));  
14.      dlclose(dl);  
15. return 0;  
16. }


3.2.2.2 编译

编译main.c生成可执行程序,动态库已创建

1. gcc -o main main.c -lld // 注:使用libld.so库进行系统调用


4 gcc中一些参数的作用

4.1 -l 参数和 -L参数

-l 参数就是用来指定程序要链接的库, -l 参数紧接着就是库名, 那么库名跟真正的库文件名有什么关系呢?拿数学库来说,他的库名是m ,他的库文件名是 libm.so ,很容易看出,把库文件名的lib和尾.so 去掉就是库名了. 如第三方库名字叫做libtest.so 那么我们只需要把libtest.so 拷贝到 /usr/lib 或者 /usr/local/lib里,在编译时加上 -ltest 参数,我们就能用上libtest.so库了.

注意:要用libtest.so 库里的函数,我们还需要与libtest.so 配套的头文件.eg:gcc -o test test.c -ltest(libtest.so 的隐式调用)

—L 参数指定程序要链接的库所在的目录,可以指定库所在目录的相对路径或者绝对路径。

1. gcc -o main main.c -L. -ladd  
2. gcc -o main main.c -L/home/user/test/ -ladd


4.2 -include 和-I参数

-include 用来包含头文件,但是一般情况下包含头文件都在源码里用#include xxx 实现, -include 参数很少用. -I参数用来指定头文件目录.

/usr/include 目录一般是不用指定的,gcc 知道去那里找,但是如果头文件不在 /usr/include 里我们就要用 -I参数指定. 比如 头文件放在 /myinclude目录里,那么便以命令行就加上-I/myinclude 参数,如果不加就会得到 "xx.h: No such file or deirectory "的错误. (注-I参数也可以用相对路径,比如当前目录,可以用 -I. 来指定).也可以通过设置环]境变量C_INCLUDE_PATH来指定头文件目录,怎样设置可参看

5 静态库和动态库链接时的搜索路径

5.1 静态库链接时搜索路径

1. ld会去找gcc命令中的参数-L

2. 再找gcc的环境变量LIBRARY_PATH

3. 再找内定目录 /lilb、/usr/lib、/usr/local/lib  这是当初compile gcc时写在程序内的

5.2 动态连接时执行时的搜索路径

1. 编译目标代码时指定的动态库搜索路径

    gcc 使用-R或-rpath选项来在编译时就指定库的查找路径,并且该库的路径信息保存在可执行文件中,运行时它会直接到该路径查找库。

1. // 使用以下两种方式都可以,-Wl选项告诉编译器将后面的参数传递给连接器  
2. #gcc -o main main.c -L. ladd -Wl,-R.  
3. #gcc -o main main.c -L. ladd -Wl,rpath=.  
4. #gcc -o main main.c -L. ladd -Wl,rpath .


2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径

    LD_LIBRARY_PATH设定是全局的,过多的使用可能会影响到其他应用程序的运行,所以多用在调试。  

1. // 当前调试时可以设置LD_LIBRARY_PATH环境变量  
2. #LD_LIBRARY_PATH=.  
3. #export LD_LIBRARY_PATH  
4. // 或  
5. #setenv LD_LIBRARY_PATH

3. 配置文件 /etc/ld.so.conf 中指定的动态库搜索路径

设置可参考 ldd命令

4. 默认的动态库搜索路径 /lib

5. 默认的动态库搜索路径 /usr/lib

5.3 有关环境变量

1. LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径

2. LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径


5.4 库的链接时路径和运行时路径

现代连接器在处理动态库时将链接时路径(Link-time path)和运行时路径(Run-time path)分开,用户可以通过-L指定链接时库的路径,通过-R(或-rpath)指定程序运行时库的路径,大大提高了库应用的灵活性。


6 静态库和动态库的命名规则

6.1 Linux静态库的命名规则

static library filename =  
 lib +<library name> +  
 .a

静态库文件名中间那一部分是库的实际名称,链接器需要使用这个名称来进行链接。

6.2 Linux动态库命名规则

dynamic library filename =  
 lib + <library name> +  
 .so + <library version information>

动态库文件名中间那一部分是库的实际名称,链接器会在随后的构建时库文件搜索过程中使用这个名称,装在器也会在运行时库文件搜索过程中使用这个名称。


6.3 动态库的版本信息

dynamic library version information =  
 <M>.<m>.<p>

其中,每个助记符可能使用一个或多个数字来表示

M:主版本号

m:次版本号

p:补丁(很小的代码改动)版本号


6.4 动态库的soname

library  
 soname = lib + <library  
 name> +  
 .so + <library  
 major version digit(s)>

举例来说:

库文件libz.so.1.2.3.4的soname则是libz.so.1

实际上只有主版本号的数字在库soname中起作用,这意味着即使库的次版本号是不同的,也可能使用同一个soname值来表示。

动态库的soname通产由链接器嵌入二进制库文件的专有ELF字段中。通常使用特定的链接器选项,将表示库soname的字符串传递给链接器。

1. // eg:  
2. $ gcc -shared <list of object files> -Wl, -soname, libfoo.so.1 -o libfoo.so.1.0.0


7 参考文章

参考文章6:linking with -Wl,-rpath and $(prefix)