在编写代码中,调用库函数是一个省时省力的做法,那么在具体的操作系统中库是如何编写实现的呢,这里记录一下学习过程
为什么要引入库的概念
- 函数模块的功能相同,实现代码也相同,通过对它们进行封装为库,方便模块之间的调用,避免代码重复。
- 封装另外一个目的是针对接口编程,对实现代码进行保密,利于代码保护和代码升级。
静态库vs共享库
Linux下的库有两种:静态库(libxxx.a)和共享库(libxxx.so)
二者的不同点在于代码被载入的时刻不同
- 静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
- 共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。
共享库的两种使用方式
- 动态链接(隐式调用)
- 动态调用(显式调用,利用lib/ld.so提供的函数,利用函数指针,动态调用一个类库中包含的函数,不需要知道动态库的头文件)
隐式调用
当创建可执行文件时,静态执行一些链接(共享库的重定位和符号表信息,而非代码和数据),然后在应用程序加载时,动态完成链接过程。
下面用一个例子来说明,我的大体思路是将打印”hello world!“作为一个函数包装到共享库中供外界调用
编写要被编译成共享库的文件
1 | // mylib.h |
1 | // mylib.c |
生成共享库文件libmylib.so
1 | gcc -shared -fPIC -o libmylib.so mylib.c |
-shared选项来指示链接器创建一个共享的目标文件(即共享库)
-fPIC选项指示编译器生成与位置无关的代码
编写测试文件
1 | // test.c |
编译测试文件
1 | gcc -o test test.c ./libmylib.so |
在可执行文件test中没有拷贝任何libmylib.so真正的代码和数据节,而是由链接器拷贝了一些重定位和符号表信息,它们使得运行时动态链接器可以解析libmylib.so中代码和数据的引用
显式调用
应用程序在运行过程中(连接库的过程直接写在代码里)要求动态链接器加载和链接任意共享库,而无需编译时链接那些库到应用中。
Linux系统为应用程序在运行过程中加载和链接共享库提供了一组API:
1 |
|
利用上一部分写好的共享库,编写测试文件
1 | // test2.c |
编译
1 | gcc -o test2 test2.c -ldl |
-ldl:表示生成的对象模块需要用到共享库