Makefile和gcc編譯相關

成果都是別人的,只是按我的理解重新摘錄,大言不慚說原創,望諒解。

一、Makefile

1.1 makefile規則

轉自百度百科
讓我們先來粗略地看一看Makefile的規則。
target … : prerequisites …
command


目標:依賴
執行指令 …
target也就是一個目標文件,可以是Object File,也可以是執行文件。還可以是一個標籤(Label)。
① prerequisites就是,要生成那個target所需要的文件或是目標。
② command也就是make需要執行的命令。(任意的Shell命令)
這是一個文件的依賴關係,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行(command一定要以Tab鍵開始,否則編譯器無法識別command),減少重複編譯,提高了其軟件工程管理效率。

1.2 預定義變量

CC: C編譯器的名稱,默認值爲 cc。
CPP: C 預編譯器的名稱,默認值爲 $(CC) -E。
CPPFLAGS: C 預編譯的選項。
CCFLAGS: C 編譯器的選項。

CXX: C++編譯器的名稱,默認值爲 g++。
CXXFLAGS: C++ 編譯器的選項。
AR: 歸檔維護程序的名稱,默認值爲 ar。
ARFLAGS: 歸檔維護程序的選項。
AS: 彙編程序的名稱,默認值爲 as。
ASFLAGS: 彙編程序的選項。

$@ 表示目標文件
$^ 表示所有的依賴文件
$< 表示第一個依賴文件
$? 表示比目標還要新的依賴文件列表

$* 不包含擴展名的目標文件名稱。
$+ 所有的依賴文件,以空格分開,並以出現的先後爲序,可能包含重複的依賴文件。
$% 如果目標是歸檔成員,則該變量表示目標的歸檔成員名稱。
例如,如果目標名稱爲mytarget.so (image.o),則 @爲mytarget.so,而
% 爲 image.o。

1.3 := ?= += =的區別

這部分轉自
https://www.cnblogs.com/wanqieddy/archive/2011/09/21/2184257.html
= 是最基本的賦值
:= 是覆蓋之前的值
?= 是如果沒有被賦值過就賦予等號後面的值
+= 是添加等號後面的值
1)、“=”
make會將整個makefile展開後,再決定變量的值。也就是說,變量的值將會是整個makefile中最後被指定的值。看例子:

x = foo
y = $(x) bar
x = xyz

在上例中,y的值將會是 xyz bar ,而不是 foo bar 。
2)、“:=”
“:=”表示變量的值決定於它在makefile中的位置,而不是整個makefile展開後的最終值。

x := foo
y := $(x) bar
x := xyz

在上例中,y的值將會是 foo bar ,而不是 xyz bar 了。

1.4 wildcard等函數

1)、wildcard : 擴展通配符
2)、notdir : 去除路徑
3)、patsubst :替換通配符
例子:
建立一個測試目錄,在測試目錄下建立一個名爲sub的子目錄

$ mkdir test
$ cd test
$ mkdir sub

在test下,建立a.c和b.c2個文件,在sub目錄下,建立sa.c和sb.c2 個文件。

建立一個簡單的Makefile

src=$(wildcard *.c ./sub/*.c)
dir=$(notdir $(src))
obj=$(patsubst %.c,%.o,$(dir) )

all:
@echo $(src)
@echo $(dir)
@echo $(obj)
@echo "end"

執行結果分析:
第一行輸出
a.c b.c ./sub/sa.c ./sub/sb.c
wildcard把 指定目錄 ./ 和 ./sub/ 下的所有後綴是c的文件全部展開。

第二行輸出
a.c b.c sa.c sb.c
notdir把展開的文件去除掉路徑信息

第三行輸出
a.o b.o sa.o sb.o

在$(patsubst %.c,%.o,$(dir) )中,
patsubst把$(dir)中的變量符合後綴是.c的全部替換成.o。
或者可以使用obj=$(dir:%.c=%.o),效果也是一樣的。

二、gcc

2.1 Linux靜態庫.a與動態庫.so的生成與區別

轉自Linux靜態庫.a與動態庫.so的生成與區別
如果有公司需要使用你們產品的一部分功能(通過代碼調用這些功能),如果不想提供源代碼,那麼就可以通過封裝成庫文件的形式提供給對方使用。
.o文件 :二進制目標文件,可用於打包成庫文件也可以鏈接生成可執行文件;
c文件編譯後鏈接,生成可執行文件。

gcc  test1.c test2.c test3.c test_main.c -o test_main
./test_main

將.o目標文件鏈接生成可執行文件

gcc -c  test1.c test2.c test3.c test_main.c//編譯成.o目標文件
gcc  test1.o test2.o test3.o test_main.o -o test_main_1//把.o文件鏈接成可執行文件
./test_main_1

1).a文件 :靜態庫文件,靜態庫在編譯時已經被鏈接到目標代碼中,運行程序不依賴該靜態庫文件;
優點:將程序使用的函數的機器碼複製到最終的可執行文件中,提高了運行速度;如果庫函數改變,整個程序需要重新編譯
缺點:所有需用到靜態庫的程序都會被添加靜態庫的那部分內容,使得可執行代碼量相對變多,佔用內存和硬盤空間.

ar rc libtest.a test1.o test2.o test3.o//把.o文件打包成.a的庫文件 
gcc test_main.c -L. -ltest -o test_main_a//鏈接生成可執行文件 
./test_main_a//運行測試程序 
rm libtest.a //刪除靜態庫文件 
./test_main_a//同樣正常運行程序

2).so文件: 動態庫文件,在程序運行的時候載入動態庫文件,程序要正常運行必須依賴於它需要使用的動態庫文件;
優點:只有在程序執行的時候, 那些需要使用的函數代碼才被拷貝到內存中。動態庫在內存中可以被被多個程序使用,又稱之爲共享庫,節約了內存和磁盤空間,以時間效率去換取空間效率;當調用接口沒改變,庫文件發生改變重新編譯,而調用的主程序可不用重新編譯;
缺點:運行速度不及靜態庫文件;
靜態庫與動態庫的選取使用,請結合自己的場景進行取捨.
3).so庫文件的封裝以及使用

OBJS:=adTorgb.cpp.o  descriptors.c.o  error.c.o  linux.c.o  satusbimage.cpp.o  usb.c.o

#把所有[.c]文件編譯成[.o]文件
#-fPIC; 代表編譯爲位置獨立的代碼,滿足了不同的進程對所加載動態庫的共享;
#-c; 表示只編譯源文件但不鏈接;
#$<; 表示所搜索到與第一個相匹配的文件,即第一個[.c]文件;
#-o; 指定輸出文件名;
#$@; 與[.c]文件相對應的[.o]文件;
#-I.; 需用到的頭文件在本目錄中找.
%.c.o:%.c 
        gcc -fPIC -c $< -o $@ -I.

%.cpp.o:%.cpp
        g++ -fPIC -c $< -o $@ -I.
        
#-shared: 該選項指定生成動態連接庫
all:$(OBJS)
        @echo "Compile..."
        g++ -shared -fpic -o libsatusb.so $(OBJS)
        @echo "End"

clean:
        rm -fr *.o *.so

測試函數文件夾中的Makefile

all:
		#-L./lib: 編譯時到當前路徑的lib文件夾中去需找libsatusb.so庫文件
        gcc satimagetest.c -L./lib -lsatusb -o satimagetest
		#如果要運行編譯完成的可執行文件,必須得設置下環境變量(執行程序時會去鏈
		#接這個.so庫文件,如果不設置環境變量,就找不到該庫文件,程序執行失敗),
		#或者把生成的.so庫文件拷貝到已設置的路徑下(可以查環境變量
		#LD_LIBRARY_PATH,但移植性不高)。
#export LD_LIBRARY_PATH=/opt/satImage/lib/:$LD_LIBRARY_PATH

2.2 gcc常用選項及 -I -L -l的區別

1) 轉自GCC簡介
①許多情況下,頭文件和源文件會單獨存放在不同的目錄中。例如,假設存放源文件的子目錄名爲./src,而包含文件則放在層次的其他目錄下,如./inc。當我們在./src 目錄下進行編譯工作時,如何告訴GCC到哪裏找頭文件呢?方法如下所示:

gcc test.c –I../inc -o test

上面的命令告訴GCC包含文件存放在./inc 目錄下,在當前目錄的上一級。如果在編譯時需要的包含文件存放在多個目錄下,可以使用多個-I 來指定各個目錄。
②警告功能
當GCC在編譯過程中檢查出錯誤的話,它就會中止編譯;但檢測到警告時卻能繼續編譯生成可執行程序,因爲警告只是針對程序結構的診斷信息,它不能說明程序一定有錯誤,而是存在風險,或者可能存在錯誤。雖然GCC提供了非常豐富的警告,但前提是你已經啓用了它們,否則它不會報告這些檢測到的警告。
在衆多的警告選項之中,最常用的就是-Wall選項。該選項能發現程序中一系列的常見錯誤警告,該選項用法舉例如下:

gcc -Wall test.c -o test

該選項相當於同時使用了下列所有的選項:

◆unused-function:遇到僅聲明過但尚未定義的靜態函數時發出警告。
◆unused-label:遇到聲明過但不使用的標號的警告。
◆unused-parameter:從未用過的函數參數的警告。
……
2)轉自多個文件目錄下Makefile的寫法

 gcc -o hello hello.c -I /home/hello/include -L /home/hello/lib -lworld

上面這句表示在編譯hello.c時:
-I /home/hello/include表示將/home/hello/include目錄作爲第一個尋找頭文件的目錄,尋找的順序是:/home/hello/include–>/usr/include–>/usr/local/include。
-L /home/hello/lib表示將/home/hello/lib目錄作爲第一個尋找庫文件的目錄,尋找的順序是:/home/hello/lib–>/lib–>/usr/lib–>/usr/local/lib。
-lworld表示在上面的lib的路徑中尋找libworld.so動態庫文件(如果gcc編譯選項中加入了“-static”表示尋找libworld.a靜態庫文件)

2.3 gcc同時使用靜態庫和動態庫鏈接

轉自GCC同時使用靜態庫和動態庫鏈接
1) 在應用程序需要連接外部庫的情況下,linux默認對庫的連接是使用動態庫,在找不到動態庫的情況下再選擇靜態庫。使用方式爲:

gcc test.cpp -L. -ltestlib

如果當前目錄有兩個庫 libtestlib. so libtestlib.a,則肯定是連接 libtestlib. so。如果要指定爲連接靜態庫則使用:

gcc test.cpp -L. -static -ltestlib

使用靜態庫進行連接。

當對動態庫與靜態庫混合連接的時候,使用-static會導致所有的庫都使用靜態連接的方式。這時需要作用-Wl的方式:

gcc test.cpp -L. -Wl,-Bstatic -ltestlib  -Wl,-Bdynamic -ltestdll 

另外還要注意系統的運行庫使用動態連接的方式,所以當動態庫在靜態庫前面連接時,必須在命令行最後使用動態連接的命令才能正常連接,如:

gcc test.cpp -L. -Wl,-Bdynamic -ltestdll -Wl,-Bstatic -ltestlib  -Wl,-Bdynamic

最後的-Wl,-Bdynamic表示將缺省庫鏈接模式恢復成動態鏈接。
2)
場景是這樣的。我在寫一個Nginx模塊,該模塊使用了MySQL的C客戶端接口庫libmysqlclient,當然mysqlclient還引用了其他的庫,比如libm, libz, libcrypto等等。對於使用mysqlclient的代碼來說,需要關心的只是mysqlclient引用到的動態庫。大部分情況下,不是每臺機器都安裝有libmysqlclient,所以我想把這個庫靜態鏈接到Nginx模塊中,但又不想把mysqlclient引用的其他庫也靜態的鏈接進來。
  我們知道gcc的-static選項可以使鏈接器執行靜態鏈接。但簡單地使用-static顯得有些’暴力’,因爲他會把命令行中-static後面的所有-l指明的庫都靜態鏈接,更主要的是,有些庫可能並沒有提供靜態庫(.a),而只提供了動態庫(.so)。這樣的話,使用-static就會造成鏈接錯誤。

之前的鏈接選項大致是這樣的:
CORE_LIBS="$CORE_LIBS -L/usr/lib64/mysql -lmysqlclient -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto"

##修改過:
CORE_LIBS="$CORE_LIBS -L/usr/lib64/mysql -Wl,-Bstatic -lmysqlclient\ -Wl,-Bdynamic -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto"

其中用到的兩個選項:-Wl,-Bstatic和-Wl,-Bdynamic。這兩個選項是gcc的特殊選項,它會將選項的參數傳遞給鏈接器,作爲鏈接器的選項。比如-Wl,-Bstatic告訴鏈接器使用-Bstatic選項,該選項是告訴鏈接器,對接下來的-l選項使用靜態鏈接;-Wl,-Bdynamic就是告訴鏈接器對接下來的-l選項使用動態鏈接。

附有用的網址

GCC編譯器30分鐘入門教程
gcc/g++ 鏈接庫的編譯與鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章