深入理解C語言----動態庫 & 靜態庫 & 連結

simon_夏發表於2014-04-21

庫是程式碼共享的主要方式,動態庫和靜態庫的主要區別在於他們連結形式的不同(靜態和動態連結),它們都是目標檔案的集合,再加上一些索引表項來表徵各檔案的資訊。通常,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第七章和《程式設計師的自我修養》



相關文章