庫是代碼共享的主要方式,動态庫和靜态庫的主要差別在于他們連結形式的不同(靜态和動态連結),它們都是目标檔案的集合,再加上一些索引表項來表征各檔案的資訊。通常,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;
#endif
append.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
靜态庫選項r 表示将後面的檔案清單添加到檔案包,如果檔案包不存在就建立它,如果檔案包中已有同名檔案就替換成新的。c表示建立一個庫,s 是專用于生成靜态庫的,表示為靜态庫建立索引,這個索引被連結器使用。ranlib 指令也可以為靜态庫建立索引,以上指令等價于:
ar rc libqueue.a append.o serve.o
ranlib libqueue.a
最後即可生存我們的主程式:
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第七章和《程式員的自我修養》