(主要內容參考自 MAXSOLAR'S LINUX BLOG - Makefile 範例教學)
- 傳統的編譯方式
- 編譯的過程是將原始碼(foo.c)先使用 -c 參數編譯成 Object file(foo.o),然後鏈結一個函式庫成為二進位檔(binary)如下。[-c : Compile]
- 接下來有更多 Makefile 的參數出現了,一一介紹如下:
- -c:編譯但不進行鏈結,會產生一個跟原始碼相同名稱但副檔名為.o的目的檔。
- -O:表示最佳化的程度,-O預設就是-O1,可以指定成-O2或-O3,數字越大表示最佳化程度越好,但是會增加編譯時間。
- -g:把偵錯資訊也編譯進去,當你有需要使用GDB軟體進行偵錯,必須加入-g使GDB能夠讀取。一般情況下可以不用-g,因為它也會增加binary大小。
- -Wall:顯示警告訊息,使用這個參數會在編譯時顯示更多的警告訊息。這個參數相當有用,特別是找不到 libs/headers 之類的問題。
- -ansi:使用相容ANSI標準的編譯方式,ANSI 是 American National Standards Institute,即美國國家標準學會的簡稱。-ansi 可以增加程式的可移植性。
- $SACLIB:是一個路徑變數名稱,必須被指定正確的值。執行這個命令前必須先確定這個變數是有被指派到正確路徑才行。.a 檔是一個靜態函式(static library),關於靜態跟共享的觀念稍候解釋。
- 再來更多吧!假設今天要編譯 main 這隻程式,source codes 中有 main.c, foo.c, target.h,並且需要 /usr/local/moreFoo/lib/libpthread.so 這個共享函式,以及 /usr/local/moreFoo/include 裡面的 headers;這麼複雜的情況又該怎麼作呢?
- -I:需要 include 某些 headers 所在的目錄,通常 include 目錄都放置headers,利用-I使編譯器知道去哪裡找原始碼裡宣告的 header。gcc預設會去尋找headers的目錄大致有:
- /usr/include
- /usr/local/include
- /usr/src/linux-headers-`uname -r`/include
- /usr/lib/gcc/i486-linux-gnu/UR_GCC_VERSION/include
- Current Dictionary
- -l:表示編譯過程需要一個 library。-lpthread 代表需要一個名為 libpthread.so的函式。
- -L:需要額外鏈結函式庫所在的目錄,有時候程式碼經常會呼叫一些函數(methods, functions 或是 subroutines),而這些函數是使用其他人預先寫好的、已經編譯成函式(例如 libpthread.so )供人使用的話,我們就不必自己從頭寫過。gcc 預設會去找函式的目錄大致有:
- /lib
- /usr/lib
- /lib/modules/`uname -r`/kernel/lib
- /usr/src/linux-headers-`uname -r`/lib
- /usr/local/lib
- Current Dictionary
- 靜態、共享與動態鏈結函式庫 輪子不必重複發明 -- 人家寫好的方法我們可以直接拿來用。不過很多時候,這些方法可能因為某些因素,希望提供給別人使用卻又不希望公佈原始碼,這時候編譯成 libraries 是最好的選擇。
- 靜態函式(static libraries) 靜態函式其實就是將一系列 .o 檔打包起來,因此它可以直接視為一個巨大的 .o 檔。打造出一個靜態函式的方法很簡單:
- 共享函式(shared libraries) 共享函式跟靜態函式的觀念剛好相反,程式在執行時必須能夠找到相依的函式,否則執行時會出現錯誤訊息。製作一個共享函式的方法也很簡單:
- 把 liboperator.so 複製或是作一個連結到 /usr/lib 裡。
- 修改 /etc/ld.so.conf,把 /usr/local/foo/lib 加進系統 libraries 的搜尋範圍內。
- 設定 LD_LIBRARY_PATH 變數,累加該路徑進來:如果你不是系統管理員,前兩個方法根本沒辦法執行。我們只好自己加到 ~/.profile 裡:
- 改用靜態函式進行鏈結。
- 動態函式(dynamic libraries) 動態函式跟共享函式非常類似,唯一的差別在於程式執行時期並不會去檢查該函式是否存在,而是程式執行到某功能時才進行檢查。這種動態載入的技術最常用在瀏覽器或是大型程式的外掛程式,當有需要用到這個功能時才載入進來。而製作一個動態函式較為麻煩。
gcc foo.c -o foo可在拆解成如下的動作:
gcc foo.c -c
gcc foo.o -o foo
gcc foo1.c $SACLIB/sacio.a -O3 -g -Wall -ansi -o foo1
gcc main.c foo.c -I /usr/local/moreFoo/include -L /usr/local/moreFoo/lib -lpthread -O3 -ansi -o main以上新出現的參數定義如下:
gcc operator.c -c或者
ar crsv liboperator.a operator.o
gcc -static operator.c -loperator兩種方法皆能產生 liboperator.a。假設這個靜態函式在 /usr/local/foo/lib/ 裡,編譯時要與靜態函式作鏈結也很容易:
gcc main.c /usr/local/foo/lib/liboperator.a -o main把靜態函式當成一般的 .o 檔一起納入 binary,也可以像這樣:
gcc main.c -L /usr/local/foo/lib -loperator -o main靜態函式將所有的功能全部打包在一起,因此 binary 會變得很巨大,但是執行這個程式的所有功能都已滿足,不會再有 libraries 相依性的問題。但是缺點在於當某些libraries的功能有所更新時,這個程式就必須重新編譯,無法享受到即時更新的優點。通常商業軟體以及嵌入式系統等功能異動較少的程式,會傾向使用靜態函式。
gcc -shared operator.c -o liboperator.so或是先編譯出目的檔再進行鏈結:
gcc -c operator.c產生出 liboperator.so。假設這個共享函式在 /usr/local/foo/lib/ 裡,使用共享函式進行鏈結也很容易:
gcc -shared operator.o -o liboperator.so
gcc main.c /usr/local/foo/lib/liboperator.so -o main也可以像這樣:
gcc main.c -L /usr/local/foo/lib -loperator -o main共享函式在程式啟動時期會檢查是否存在。以一個分別鏈結了靜態函式與共享函式的 binary 而言,執行的結果大有差別。以靜態函式鏈結的 main 程式可以順利執行,但是假設系統預設尋找函式庫的路徑裡找不到 liboperator.so,以共享函式鏈結的main程式則會出現錯誤訊息:
./main: error while loading shared libraries: liboperator.so: cannot open shared object file: No such file or directory
這時解決的方法有四種:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/foo/lib
gcc -c -fPIC operator.c其中的 -fPIC 是產生 position-independent code,也可以用 -fpic,撰寫呼叫動態函式的程式碼也需要傳入相關參數。
gcc -shared operator.o -o liboperator.so
0 意見:
張貼留言