程序清理
有一個C程序登澜,用于實現(xiàn)算法和數(shù)據(jù)結構 (比如棧和相關的操作)她按。在同一個程序中牛隅,還有用于測試的main()函數(shù),結構體定義酌泰,函數(shù)原型,typedef等等匕累。
這樣的做法非常不“環(huán)绷晟玻”。算法的實際運用和算法的實現(xiàn)混在一起欢嘿。如果我想要重復使用之前的源程序衰琐,必須進行許多改動,并且重新編譯炼蹦。最好的解決方案是實現(xiàn)模塊化: 只保留純粹的算法實現(xiàn)羡宙,分離頭文件,并編譯一個庫(library)掐隐。每次需要使用庫的時候(比如使用棧數(shù)據(jù)結構)狗热,就在程序中include頭文件,連接庫虑省。這樣匿刮,不需要每次都改動源程序。
如何在UNIX環(huán)境中創(chuàng)建共享庫 (shared library)探颈。UNIX下熟丸,共享庫以so為后綴(shared object)。共享庫與Windows下的DLL類似伪节,是在程序運行時動態(tài)連接光羞。多個進程可以連接同一個共享庫绩鸣。
/* By Vamei */
/* use single-linked list to implement stack */
#include <stdio.h>
#include <stdlib.h>
typedef struct node *position;
typedef int ElementTP;
// point to the head node of the list
typedef struct node *STACK;
struct node {
ElementTP element;
position next;
};
STACK init_stack(void);
void delete_stack(STACK);
ElementTP top(STACK);
void push(STACK, ElementTP);
ElementTP pop(STACK);
int is_null(STACK);
void main(void)
{
ElementTP a;
int i;
STACK sk;
sk = init_stack();
push(sk, 1);
push(sk, 2);
push(sk, 8);
printf("Stack is null? %d\n", is_null(sk));
for (i=0; i<3; i++) {
a = pop(sk);
printf("pop: %d\n", a);
}
printf("Stack is null? %d\n", is_null(sk));
delete_stack(sk);
}
/*
* initiate the stack
* malloc the head node.
* Head node doesn't store valid data
* head->next is the top node
*/
STACK init_stack(void)
{
position np;
STACK sk;
np = (position) malloc(sizeof(struct node));
np->next = NULL; // sk->next is the top node
sk = np;
return sk;
}
/* pop out all elements
* and then delete head node
*/
void delete_stack(STACK sk)
{
while(!is_null(sk)) {
pop(sk);
}
free(sk);
}
/*
* View the top frame
*/
ElementTP top(STACK sk)
{
return (sk->next->element);
}
/*
* push a value into the stack
*/
void push(STACK sk, ElementTP value)
{
position np, oldTop;
oldTop = sk->next;
np = (position) malloc(sizeof(struct node));
np->element = value;
np->next = sk->next;
sk->next = np;
}
/*
* pop out the top value
*/
ElementTP pop(STACK sk)
{
ElementTP element;
position top, newTop;
if (is_null(sk)) {
printf("pop() on an empty stack");
exit(1);
}
else {
top = sk->next;
element = top->element;
newTop = top->next;
sk->next = newTop;
free(top);
return element;
}
}
/* check whether a stack is empty*/
int is_null(STACK sk)
{
return (sk->next == NULL);
}
上面的main()部分是用于測試,不屬于功能模塊纱兑,在創(chuàng)建庫的時候應該去掉全闷。
程序中的一些聲明,會被重復利用萍启。比如:
typedef struct node *position;
typedef int ElementTP;
// point to the head node of the list
typedef struct node *STACK;
struct node {
ElementTP element;
position next;
};
STACK init_stack(void);
void delete_stack(STACK);
ElementTP top(STACK);
void push(STACK, ElementTP);
ElementTP pop(STACK);
int is_null(STACK);
這一段程序聲明了一些結構體和指針总珠,以及棧操作的函數(shù)原型。當我們其他程序中調(diào)用庫時 (比如創(chuàng)建一個棧勘纯,或者執(zhí)行pop操作)局服,同樣需要寫這些聲明。我們把這些在實際調(diào)用中需要的聲明保存到一個頭文件mystack.h驳遵。在實際調(diào)用的程序中淫奔,可以簡單的include該頭文件,避免了每次都寫這些聲明語句的麻煩堤结。
經(jīng)過清理后的C程序為mystack.c:
/* By Vamei */
/* use single-linked list to implement stack */
#include <stdio.h>
#include <stdlib.h>
#include "mystack.h"
/*
* initiate the stack
* malloc the head node.
* Head node doesn't store valid data
* head->next is the top node
*/
STACK init_stack(void)
{
position np;
STACK sk;
np = (position) malloc(sizeof(struct node));
np->next = NULL; // sk->next is the top node
sk = np;
return sk;
}
/* pop out all elements
* and then delete head node
*/
void delete_stack(STACK sk)
{
while(!is_null(sk)) {
pop(sk);
}
free(sk);
}
/*
* View the top frame
*/
ElementTP top(STACK sk)
{
return (sk->next->element);
}
/*
* push a value into the stack
*/
void push(STACK sk, ElementTP value)
{
position np, oldTop;
oldTop = sk->next;
np = (position) malloc(sizeof(struct node));
np->element = value;
np->next = sk->next;
sk->next = np;
}
/*
* pop out the top value
*/
ElementTP pop(STACK sk)
{
ElementTP element;
position top, newTop;
if (is_null(sk)) {
printf("pop() on an empty stack");
exit(1);
}
else {
top = sk->next;
element = top->element;
newTop = top->next;
sk->next = newTop;
free(top);
return element;
}
}
/* check whether a stack is empty*/
int is_null(STACK sk)
{
return (sk->next == NULL);
}
#include "..."; 語句將首先在工作目錄尋找相應文件唆迁。如果使用gcc時,增加-I選項竞穷,將在-I提供的路徑中尋找唐责。
制作.so文件
我們的目標是制作共享庫,即.so文件瘾带。
首先鼠哥,編譯stack.c:
$gcc -c -fPIC -o mystack.o mystack.c
-c表示只編譯(compile),而不連接看政。-o選項用于說明輸出(output)文件名朴恳。gcc將生成一個目標(object)文件mystack.o。
注意-fPIC選項允蚣。PIC指Position Independent Code于颖。共享庫要求有此選項,以便實現(xiàn)動態(tài)連接(dynamic linking)嚷兔。
生成共享庫:
$gcc -shared -o libmystack.so mystack.o
庫文件以lib開始森渐。共享庫文件以.so為后綴。-shared表示生成一個共享庫谴垫。
這樣章母,共享庫就完成了。.so文件和.h文件都位于當前工作路徑(.)翩剪。
使用共享庫
我們編寫一個test.c乳怎,來實際調(diào)用共享庫:
#include <stdio.h>
#include "mystack.h"
/*
* call functions in mystack library
*/
void main(void)
{
ElementTP a;
int i;
STACK sk;
sk = init_stack();
push(sk, 1);
push(sk, 2);
push(sk, 8);
printf("Stack is null? %d\n", is_null(sk));
for (i=0; i<3; i++) {
a = pop(sk);
printf("pop: %d\n", a);
}
printf("Stack is null? %d\n", is_null(sk));
delete_stack(sk);
}
編譯上述程序。編譯器需要知道.h文件位置。
對于#include "..."蚪缀,編譯器會在當前路徑搜索.h文件秫逝。你也可以使用-I選項提供額外的搜索路徑,比如-I/home/vamei/test询枚。
對于#include <...>违帆,編譯器會在默認include搜索路徑中尋找。
編譯器還需要知道我們用了哪個庫文件金蜀,在gcc中:
使用-l選項說明庫文件的名字刷后。這里,我們將使用-lmystack (即libmystack庫文件)渊抄。
使用-L選項說明庫文件所在的路徑尝胆。這里,我們使用-L. (即.路徑)护桦。
如果沒有提供-L選項含衔,gcc將在默認庫文件搜索路徑中尋找。
你可以使用下面的命令二庵,來獲知自己電腦上的include默認搜索路徑:
$
gcc -print-prog-name=cc1-v
獲知庫默認搜索路徑:
$gcc -print-search-dirs
我們所需的.h和.so文件都在當前路徑贪染,因此,使用如下命令編譯:
$gcc -o test test.c -lmystack -L.
將生成test可執(zhí)行文件催享。
$./test
執(zhí)行程序
運行程序
./test: error while loading shared libraries: libmystack.so: cannot open shared object file: No such file or directory
這是因為操作系統(tǒng)無法找到庫杭隙。libmystack.so位于當前路徑,位于庫文件的默認路徑之外睡陪。盡管我們在編譯時(compile time)提供了.so文件的位置寺渗,但這個信息并沒有寫入test可執(zhí)行文件(runtime)±计龋可以使用下面命令測試:
$ldd test
ldd用于顯示可執(zhí)行文件所依賴的庫。顯示:
linux-vdso.so.1 => (0x00007fff31dff000)
libmystack.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fca30de7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fca311cb000)
這說明test可執(zhí)行文件無法找到它所需的libmystack.so庫文件炬称。
為了解決上面的問題汁果,我們可以將.so文件放入默認搜索路徑中。但有時玲躯,特別是多用戶環(huán)境下据德,我們不享有在默認搜索路徑寫入的權限。
一個解決方案是設置LD_LIBRARY_PATH環(huán)境變量跷车。比如:
$export LD_LIBRARY_PATH=.
這樣棘利,可執(zhí)行文件執(zhí)行時,操作系統(tǒng)將在先在LD_LIBRARY_PATH下搜索庫文件朽缴,再到默認路徑中搜索善玫。環(huán)境變量的壞處是,它會影響所有的可執(zhí)行程序密强。如果我們在編譯其他程序時茅郎,如果我們不小心蜗元,很可能導致其他可執(zhí)行文件無法運行。因此系冗,LD_LIBRARY_PATH環(huán)境變量多用于測試奕扣。
另一個解決方案,即提供-rpath選項掌敬,將搜索路徑信息寫入test文件(rpath代表runtime path)惯豆。這樣就不需要設置環(huán)境變量。這樣做的壞處是奔害,如果庫文件移動位置楷兽,我們需要重新編譯test。使用如下命令編譯test.c:
$gcc -g -o test test.c -lmystack -L. -Wl,-rpath=.
-Wl表示舀武,-rpath選項是傳遞給連接器(linker)拄养。
test順利執(zhí)行的結果為:
Stack is null? 0
pop: 8
pop: 2
pop: 1
Stack is null? 1