库是代码共享的主要方式,动态库和静态库的主要区别在于他们链接形式的不同(静态和动态链接),它们都是目标文件的集合,再加上一些索引表项来表征各文件的信息。通常,linux里目标文件是ELF格式,而win则为PE
静态库和静态链接
linux下静态库是以.a为后缀,而win下静态库是以lib为后缀。
静态链接是由链接器将一个或多个目标文件及静态库中所被引用的目标文件完全链接到一个可执行文件中,由链接器完成所有的工作,包括符号解析和重定位。
该可执行文件直接由驻留在内存里的加载器加载执行即可。
用一个小的静态库作为例子:
queue.h
#ifndef _QUEUE_H_ #define _QUEUE_H_ int queue[16] = {0}; int head = 0; int rear = 0; #endifappend.c
extern int queue[16]; extern int head; extern int rear; int append(int ele) { if ((rear + 1) % 16 == head) return 0; else { rear = (rear+1) % 16; queue[rear] = ele; return 1; } }serve.c
extern int queue[16]; extern int head; extern int rear; int serve() { if (head != rear ) { head = (head + 1) % 16; return queue[head]; } else return -1; }main.c
#include<stdio.h> #include"queue.h" extern int head; extern int rear; int main() { int i; for (i = 1; i <= 20; i++) { if (!append(i)) { printf("%d ",serve()); i--; } } while (head != rear) printf("%d ",serve()); return 0; }
首先,我们把两个实现的函数编译为目标文件:
gcc -c append.c gcc -c serve.c然后用ar工具打包为静态库
ar rcs libqueue.a append.o serve.o最后即可生存我们的主程序:
gcc -o queue main.c ./libqueue.a
动态库和动态链接
linux里动态库以so作为后缀,而win里则是dll
动态链接从某个角度说,可以说是把静态链接的过程进行了拆分。当创建可执行文件时,静态执行一些链接,然后在程序加载时,动态完成链接过程。程序执行时,由加载器调用动态链接器,动态链接器执行一些重定位之后,才把控制权返回给应用程序。
动态库的生成很简单:
gcc -shared -fPIC libqueue.so append.c serve.c编译时链接上该库
gcc -o queue main.c ./libqueue.so
与静态链接相比,动态链接有很多优势,比如节省了系统资源开销,方便软件升级更新。
动态装载库
还有一种更灵活的模块加载方式,在程序运行的时候进行加载和链接,即:显式运行时链接。它的优势在于不必重新启动程序,并且减少了程序的启动时间和内存使用。从格式上看它和动态库没有区别,区别在于,它的装载是通过一系列由动态链接器提供的API,具体是dlopen,dlsym,dlerror,dlclose,定义在dlfen.h中,实现在/lib/libdl.so.2里。关于这几个函数的使用可以查看相关文档。于是,上面的例子也可以实现为:main2.c
#include<stdio.h> #include<dlfcn.h> #include"queue.h" extern int head; extern int rear; int main() { void *handle; int (*serve)(); int (*append)(int); char *error; int i; if ((handle = dlopen("./libqueue.so",RTLD_LAZY))== NULL) { printf("Fail to load dynamic lib!\n"); return 1; } serve = dlsym(handle, "serve"); if ((error = dlerror()) != NULL) { printf("symbol serve not found!\n"); return 1; } append = dlsym(handle, "append"); if ((error = dlerror()) != NULL) { printf("symbol append not found!\n"); return 1; } for (i = 1; i <= 20; i++) { if (!append(i)) { printf("%d ", serve()); i--; } } while (head != rear) printf("%d ", serve()); dlclose(handle); return 0; }注意编译时带上-rdynamic参数并且链接libdl.so:
gcc -rdynamic -o queue main.c -ldl
关于链接和库是一个很大知识范围,涉及较多的底层知识,比如上文很简略的动态链接中的很多概念如延迟绑定,位置无关代码,每一个都值得理解和研究。篇幅和能力所限,本文只以一个具体的例子实现作为学习引子。更多的内容可以参考:CSAPP第七章和《程序员的自我修养》