C編譯: 動態連線庫 (.so檔案)
在“紙上談兵: ”中,我在每一篇都會有一個C程式,用於實現演算法和資料結構 (比如棧和相關的操作)。在同一個程式中,還有用於測試的main()函式,結構體定義,函式原型,typedef等等。
這樣的做法非常不“環保”。演算法的實際運用和演算法的實現混在一起。如果我想要重複使用之前的源程式,必須進行許多改動,並且重新編譯。最好的解決方案是實現模組化: 只保留純粹的演算法實現,分離標頭檔案,並編譯一個庫(library)。每次需要使用庫的時候(比如使用棧資料結構),就在程式中include標頭檔案,連線庫。這樣,不需要每次都改動源程式。
我在這裡介紹如何在UNIX環境中建立共享庫 (shared library)。UNIX下,共享庫以so為字尾(shared object)。共享庫與Windows下的DLL類似,是在程式執行時動態連線。多個程式可以連線同一個共享庫。
本文使用Ubuntu測試,使用gcc作為編譯器。
程式清理
下面程式來自紙上談兵: 棧 (stack),是棧資料結構的C實現:
/* By Vamei */ /* use single-linked list to implement stack */ #include#include 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? %dn", is_null(sk)); for (i=0; inext 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()部分是用於測試,不屬於功能模組,在建立庫的時候應該去掉。
程式中的一些宣告,會被重複利用。比如:
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);
這一段程式宣告瞭一些結構體和指標,以及棧操作的函式原型。當我們其他程式中呼叫庫時 (比如建立一個棧,或者執行pop操作),同樣需要寫這些宣告。我們把這些在實際呼叫中需要的宣告儲存到一個標頭檔案mystack.h。在實際呼叫的程式中,可以簡單的include該標頭檔案,避免了每次都寫這些宣告語句的麻煩。
經過清理後的C程式為mystack.c:
/* use single-linked list to implement stack */ #include#include #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。共享庫要求有此選項,以便實現動態連線(dynamic linking)。
生成共享庫:
$gcc -shared -o libmystack.so mystack.o
庫檔案以lib開始。共享庫檔案以.so為字尾。-shared表示生成一個共享庫。
這樣,共享庫就完成了。.so檔案和.h檔案都位於當前工作路徑(.)。
使用共享庫
我們編寫一個test.c,來實際呼叫共享庫:
#include#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? %dn", is_null(sk)); for (i=0; i 注意,我們在程式的一開始include了mystack.h。
編譯上述程式。編譯器需要知道.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可執行檔案。
使用
$./test
執行程式
執行程式
儘管我們成功編譯了test可執行檔案,但很有可能不能執行。一個可能是許可權問題。我們需要有執行該檔案的許可權,見Linux檔案管理背景知識
另一個情況是:
./test: error while loading shared libraries: libmystack.so: cannot open shared object file: No such file or directory
這是因為作業系統無法找到庫。libmystack.so位於當前路徑,位於庫檔案的預設路徑之外。儘管我們在編譯時(compile time)提供了.so檔案的位置,但這個資訊並沒有寫入test可執行檔案(runtime)。可以使用下面命令測試:
$ldd test
ldd用於顯示可執行檔案所依賴的庫。顯示:
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可執行檔案無法找到它所需的libmystack.so庫檔案。
為了解決上面的問題,我們可以將.so檔案放入預設搜尋路徑中。但有時,特別是多使用者環境下,我們不享有在預設搜尋路徑寫入的許可權。
一個解決方案是設定LD_LIBRARY_PATH環境變數。比如:
$export LD_LIBRARY_PATH=.
這樣,可執行檔案執行時,作業系統將在先在LD_LIBRARY_PATH下搜尋庫檔案,再到預設路徑中搜尋。環境變數的壞處是,它會影響所有的可執行程式。如果我們在編譯其他程式時,如果我們不小心,很可能導致其他可執行檔案無法執行。因此,LD_LIBRARY_PATH環境變數多用於測試。
另一個解決方案,即提供-rpath選項,將搜尋路徑資訊寫入test檔案(rpath代表runtime path)。這樣就不需要設定環境變數。這樣做的壞處是,如果庫檔案移動位置,我們需要重新編譯test。使用如下命令編譯test.c:
$gcc -g -o test test.c -lmystack -L. -Wl,-rpath=.
-Wl表示,-rpath選項是傳遞給聯結器(linker)。
test順利執行的結果為:
Stack is null? 0 pop: 8 pop: 2 pop: 1 Stack is null? 1
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2249/viewspace-2800875/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android-ffmpeg編譯so檔案Android編譯
- lua——alien庫實現lua呼叫C動態連結庫(dll、so)
- 編譯lua動態庫編譯
- linux編譯so庫不要生成字尾和軟連結Linux編譯
- FFmpeg編譯Android使用的so庫編譯Android
- c語言多檔案編譯C語言編譯
- 編譯 pyav 成 wheel 並使用 auditwheel 固化動態連結庫編譯
- go編譯靜態檔案到exeGo編譯
- 動態網頁(JSP 檔案)如何連線資料庫(SQL Server)--看這裡網頁JS資料庫SQLServer
- mingw下編譯zlib quazip動態庫編譯
- C#呼叫C++動態連結庫C#C++
- 【C語言】linux下多檔案編譯C語言Linux編譯
- 織夢CMS(dedecms)的資料庫連線檔案_織夢連線資料庫檔案資料庫
- Android native層動態載入so庫Android
- C語言動態庫libxxx.so的幾種使用方法C語言
- Android:JNI與NDK(二)交叉編譯與動態庫,靜態庫Android編譯
- 關於go程式的靜態連結編譯是否可以不依賴系統C庫Go編譯
- Android使用cmake編譯串列埠通訊.so檔案報錯cannot locate symbol "tcgetattr"Android編譯串列埠Symbol
- vc 編譯連線選項編譯
- 網站連線資料庫配置檔案網站資料庫
- windows和linux gcc生成動態連結庫DLL和SO並用python呼叫WindowsLinuxGCPython
- 動態連結庫與靜態連結庫
- GmSSL3.X編譯iOS和Android動態庫編譯iOSAndroid
- C語言中編譯和連結C語言編譯
- 從原始檔到可執行檔案:原始檔的預處理、編譯、彙編、連結編譯
- Maven根據pom檔案中的Profile標籤動態配置編譯選項Maven編譯
- Java讀取properties檔案連線資料庫Java資料庫
- 在AndroidStudio下使用cmake編譯出靜態連結庫的方法Android編譯
- Visual Studio 2022 靜態庫編譯編譯
- 安卓動態連結庫檔案體積最佳化探索實踐安卓
- 解決Qt編譯動態連結庫could not find or load the Qt platform plugin "windows" in.問題QT編譯PlatformPluginWindows
- [Python]批量編譯pyc檔案Python編譯
- C# 連線多種資料庫元件,類庫專案C#資料庫元件
- Android NDK祕籍--編譯靜態庫、呼叫靜態庫Android編譯
- P/Invoke之C#呼叫動態連結庫DLLC#
- 關於MNN工程框架編譯出來的靜態庫和動態庫的使用框架編譯
- C#自動檢測檔案的編碼C#
- Zookeeper C客戶端庫編譯客戶端編譯