Linux下Makefile中動態鏈接庫和靜態鏈接庫的生成與調用
背景:寫這篇博客的原因是:最近在搞嵌入式,需要交叉編譯opencv庫文件,自己寫Makefile,通過arm-linux-g++編譯、鏈接、生成可執行文件,從而實現了移植的過程。平台是Toradex的Apalis TK1,三千多元,買回來我就后悔了,全是英文資料,還各種Bug,遲遲無法上手。早知如此,還不如直接買Nvidia的Jetson TK1呢。
書歸正傳,今天寫一下Makefile文件中,動態鏈接庫和靜態鏈接庫的生成與調用。
一、概念
動態鏈接庫:是一種不可執行的二進制程序文件,它允許程序共享執行特殊任務所必需的代碼和其他資源。Windows平台上動態鏈接庫的后綴名是”.dll”,Linux平台上的后綴名是“.so”。Linux上動態庫一般是libxxx.so;相對於靜態函數庫,動態函數庫在編譯的時候並沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫里的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,所以動態函數庫的升級比較方便。
靜態鏈接庫:這類庫的名字一般是libxxx.a;利用靜態函數庫編譯成的文件比較大,因為整個函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯后的執行程序不需要外部的函數庫支持,因為所有使用的函數都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函數庫改變了,那么你的程序必須重新編譯。
Makefile:利用IDE開發調試的人員可能對Makefile不大理解,其實Makefile就是完成了IDE中的編譯、鏈接、生成等工作,並遵循shell腳本中變量定義與調用的規則。
二、編寫Makefile實現編譯與鏈接
1、准備文件
我們寫一個簡單的工程吧,此工程包含3個文件,分別是main.cpp,func.cpp和func.h。代碼如下:
1)main.cpp源文件:包含入口函數 int main()。該源文件中添加了“func.h”頭文件,在入口函數中調用func()函數。func函數中在func.cpp中定義,在func.h中原型聲明。
# main.cpp
#include "func.h"
int main()
{
func();
return 0;
}
2)func.h頭文件:對void func()函數進行原型聲明。
3)func.cpp源文件:對void func()函數的定義或實現。
# func.cpp
#include "func.h"
void func()
{
std::cout << "Hello World !" << std::endl;
}
2、編寫Makefile文件
1)定義變量
首先定義SOURCE,OBJS和TARGET變量,用於指代我們項目中的源文件、目標文件和可執行文件。
2) 設置編譯參數
CC:配置編譯器為g++,
LIBS:需要調用的鏈接庫(-l開頭,去掉lib和.so。例:對 libopencv_core.so鏈接庫的調用要寫作:-lopencv_core),
LDFLAGS:鏈接庫的路徑(-L開頭),
INCLUDE:頭文件的路徑。
3)鏈接生成
此步驟生成可執行文件(ELF),鏈接需要用到目標文件,由下一步產生
4)編譯
此步驟生成目標文件(.o)
5)清理
此步驟清理可執行文件和所有的目標文件
#######################
# Makefile
#######################
# source object target
SOURCE := main.cpp func.cpp
OBJS := main.o func.o
TARGET := main
# compile and lib parameter
CC := g++
LIBS :=
LDFLAGS := -L.
DEFINES :=
INCLUDE := -I.
CFLAGS :=
CXXFLAGS:=
# link
#$(TARGET):$(OBJS)
$(CC) -o $@ $^
# compile
#$(OBJS):$(SOURCE)
$(CC) -c main.cpp -o main.o
$(CC) -c func.cpp -o func.o
# clean
clean:
rm -fr *.o
rm -fr $(TARGET)
上述Makefile是將編譯和鏈接兩個步驟分開寫的,我們同樣可以直接從源文件生成可執行文件,自動進行編譯鏈接等工作。
方法:將上述Makefile中的:
# link
#$(TARGET):$(OBJS)
$(CC) -o $@ $^
# compile
#$(OBJS):$(SOURCE)
$(CC) -c main.cpp -o main.o
$(CC) -c func.cpp -o func.o
修改為:
all:
$(CC) -o $(TARGET) $(SOURCE)
其他內容,不作變化。
6)編譯、執行、清理
make
./main
make clean
NOTE:在執行make的時候會出現Makefile:19: *** missing separator. Stop的錯誤,提示沒有分割符,在makefile中規定必須以TAB鍵進行縮進.
執行make clean可以清楚當前目錄下編譯的文件.
2、動態鏈接庫的調用
引言:在第一部分中,我們將main.o和func.o兩個目標文件進行鏈接,便生成了main可執行文件。如果甲方並沒有提供func.cpp和func.o,只是提供了libfunc.so這個鏈接庫,我們如何生成可執行文件呢?下文就是講述如何利用動態庫鏈接生成可執行文件。
Makefile如下:
1)編譯的時候需要通過INCLUDE指明頭文件的路徑
2)鏈接的時候需要通過LDFLAGS 和 LIBS指明動態庫的路徑和名稱。[A1] 這里需要注意的是,指明動態庫名稱時需要“掐頭去尾”,例:我們需要用到 libfunc.so庫,LIBS必須定義為 -lfunc 。
/*[A1]
LDFLAGS指明動態庫的路徑;
LIBS指明動態庫的名稱,需注意動態庫名稱需要“掐頭去尾”
*/
3)執行的時候,需要把libfunc.so動態庫拷貝到系統環境變量包含的路徑下(比如/lib或/usr/lib),這樣程序在運行時才能調用到動態庫。
#######################
# Makefile
#######################
# target
TARGET := main
# compile and lib parameter
CC := g++
LDFLAGS := -L/path/to/libfunc.so
LIBS := -lfunc
DEFINES :=
INCLUDE := -I/path/to/func.h
CFLAGS :=
CXXFLAGS:=
# link
$(TARGET):main.o
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
#compile
main.o:main.cpp
$(CC) $(INCLUDE) -c $^crm -fr $(TARGET)
clean:
rm -fr *.o
rm -fr $(TARGET)
四、靜態鏈接庫的生成和調用
1、靜態鏈接庫的生成
引言:仍然利用上文中的main.cpp, func.cpp和func.h文件。下面,我們將func.cpp源文件制作成靜態鏈接庫func.a,然后調用該靜態庫對main.cpp進行編譯鏈接。
Makefile如下:
注意:AR:配置鏈接器為ar
#######################
# Makefile
######################
# compile and lib parameter
CC := g++
LIBS :=
LDFLAGS :=
DEFINES :=
INCLUDE := -I.
CFLAGS :=
CXXFLAGS:=
# link parameter
AR := ar
LIB := func.a
#link
$(LIB):func.o
$(AR) -r $@ $^
#compile
func.o:func.cpp
$(CC) -c $^ -o $@
# clean
clean:
rm -fr *.o
執行make命令之后,就可以在當前目錄生成func.a的靜態鏈接庫了。
注意:靜態鏈接庫必須“以.a結尾”。
2、靜態鏈接庫的調用
引言:在第一部分中,我們將main.o和func.o兩個目標文件進行鏈接,便生成了main可執行文件;第二部分,我們將main.o和libfunc.so進行鏈接,也可以生成main可執行文件。如果我們既沒有func.o也沒有func.so,該如何生成可執行文件呢?下文就是講述如何利用靜態庫func.a鏈接生成可執行文件。
Makefile如下:
1)編譯的時候需要通過INCLUDE指明頭文件的路徑
2)鏈接的時候需要通過LDFLAGS 和 LIBS指明靜態庫的路徑和名稱。這里不需要像動態庫那樣“掐頭去尾”,直接寫作func.a即可。
3)執行的時候,不需要拷貝func.a至環境變量包含的路徑,直接執行即可。
#######################
# Makefile
#######################
# target
TARGET := main
# compile and lib parameter
CC := g++
LDFLAGS := -L.
LIBS := func.a
DEFINES :=
INCLUDE := -I.
CFLAGS :=
CXXFLAGS:=
# link
$(TARGET):main.o
$(CC) -o $@ $^ $(LIBS)
#compile
main.o:main.cpp
$(CC) -c $^ -o $@
# clean
clean:
rm -fr *.o
rm -fr $(TARGET)
linux ar 命令的使用說明和例子
用途說明
創建靜態庫.a文件。用C/C++開發程序時經常用到,但我很少單獨在命令行中使用ar命令,一般寫在makefile中,有時也會在shell腳 本中用到。關於Linux下的庫文件、靜態庫、動態庫以及怎樣創建和使用等相關知識,參見本文后面的相關資料【3】《關於Linux靜態庫和動態庫的分析》。
常用參數 格式:ar rcs libxxx.a xx1.o xx2.o
參數r:在庫中插入模塊(替換)。當插入的模塊名已經在庫中存在,則替換同名的模塊。如果若干模塊中有一個模塊在庫中不存在,ar顯示一個錯誤消息,並不替換其他同名模塊。 默認的情況下,新的成員增加在庫的結尾處,可以使用其他任選項來改變增加的位置。
參數c:創建一個庫。不管庫是否存在,都將創建。
參數s:創建目標文件索引,這在創建較大的庫時能加快時間。(補充:如果不需要創建索引,可改成大寫S參數;如果。a文件缺少索引,可以使用ranlib命令添加)
格式:ar t libxxx.a 顯示庫文件中有哪些目標文件,只顯示名稱。
格式:ar tv libxxx.a 顯示庫文件中有哪些目標文件,顯示文件名、時間、大小等詳細信息。
格式:nm -s libxxx.a 顯示庫文件中的索引表。
格式:ranlib libxxx.a 為庫文件創建索引表。
使用示例
示例一 在shell腳本中使用Bash代碼OS=`uname -r` ar rcs libhycu.a.$OS *.o
示例二 在makefile中使用 Makefile代碼 $(BIN1): $(BIN1_OBJS) ar rcs $@ $^
示例三 創建並使用靜態庫
第一步:編輯源文件,test.h test.c main.c。其中main.c文件中包含main函數,作為程序入口;test.c中包含main函數中需要用到的函數。vi test.h test.c main.c[A2] /*Vi 多個源文件*/
第二步:將test.c編譯成目標文件。 gcc -c test.c 如果test.c無誤,就會得到test.o這個目標文件。 如果是gcc test.c 則默認生成a.out文件
第三步:由.o文件創建靜態庫。 ar rcs libtest.a test.o
第四步:在程序中使用靜態庫。 gcc -o main main.c -L. -ltest[A1] 因為是靜態編譯,生成的執行文件可以獨立於.a文件運行。 /*GCC 命令行詳解 -L 指定庫的路徑 -l 指定需連接的庫名*/
第五步:執行。 ./main
示例四創建並使用動態庫
第一步:編輯源文件,test.h test.c main.c。其中main.c文件中包含main函數,作為程序入口;test.c中包含main函數中需要用到的函數。 vi test.h test.c main.c
第二步:將test.c編譯成目標文件。 gcc -c test.c 前面兩步與創建靜態庫一致。
第三步:由.o文件創建動態庫文件。 gcc -shared -fPIC -o libtest.so test.o
第四步:在程序中使用動態庫。 gcc -o main main.c -L. -ltest 當靜態庫和動態庫同名時,gcc命令將優先使用動態庫。
第五步:執行。 LD_LIBRARY_PATH=. ./main
如果想創建一個動態鏈接庫,可以使用 GCC 的-shared
選項。輸入文件可以是源文件、匯編文件或者目標文件。
-shared該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當於一個可執行文件。
另外還得結合-fPIC
選項。-fPIC 選項作用於編譯階段,告訴編譯器產生與位置無關代碼(Position-Independent Code);這樣一來,產生的代碼中就沒有絕對地址了,全部使用相對地址,所以代碼可以被加載器加載到內存的任意位置,都可以正確的執行。這正是共享庫所要求的,共享庫被加載時,在內存的位置不是固定的。
轉自:https://blog.csdn.net/u011964923/article/details/73297443