Makefile 學習

Makefile 學習

一、靜態模式

  1. objects = foo.o bar.o 
  2. all: $(objects) 
  3. $(CC) $(CFLAGS) -o -o $@ $^ 
  4. $(objects): %.o: %.
  5. $(CC) -c $(CFLAGS) $< -o $@ 

上面的例子中,指明瞭咱們的目標從$object中獲取,「%.o」代表要全部以「.o」結尾的目標,也就是「foo.o bar.o」,也就是變量$object集合的模式,而依賴模式「%.c」則取模式「%.o」的「%」,也就是「foobar」,併爲其加下「.c」的後綴,因而,咱們的依賴目標就是「foo.cbar.c」。而命令中的「$<」和「$@」則是自動化變量,「$<」表示全部的依賴目標集(也就是「foo.c bar.c」),「$@」表示目標集(也就是oo.o bar.o」)。因而,上面的規則展開後等價於下面的規則:javascript

  1. foo.o : foo.c 
  2. $(CC) -c $(CFLAGS) foo.c -o foo.o 
  3. bar.o : bar.c 
  4. $(CC) -c $(CFLAGS) bar.c -o bar.o 

這是一個進行了提高的列子:java

  1. files = foo.elc bar.o lose.o 
  2. $(filter %.o,$(files)): %.o: %.
  3. $(CC) -c $(CFLAGS) $< -o $@ 
  4. $(filter %.elc,$(files)): %.elc: %.el 
  5. emacs -f batch-byte-compile $< 

使用了filter進行單獨過濾處理。c++


二、特殊命令標記符號

  • -號,會標記全部後面的命令都是返回成功,也就是忽略失敗。數組

  • @號,會在make的時候,只是執行命令,而不輸出命令自己。架構

  • make的 -n 參數,加上這個參數以後,只會展開命令,而不真的執行,能夠幫助調試腳本。jvm

  • .PHONY:clean 用來定一個僞目標,好比這裏定義了一個清理操做。ide

  • [Tab]鍵後面跟着的會被看成命令來執行。多個命令有依賴關係的時候,要寫在一行,而且用分號分隔。函數

  • =、:=、?=、+=的區別,=能夠在前面的代碼用後面的變量,:=則不能夠。?=被賦值的變量若是沒有定義過,就是等於後面的值,若是定義過,則什麼都不作。+=用於追加。學習

  • override 定義了一個覆蓋操做。ui


三、make的環境變量

有兩個變量,一個是SHELL,一個是 MAKEFLAGS ,這兩個變量無論你是否export,其老是要傳遞到下層Makefile中,其餘環境變量須要export 導出一下。可是make命令中的有幾個參數並不往下傳遞,它們是「-C」,「-f」,「-h」「-o」和「-W」(有關Makefile參數的細節將在後面說明)
MAKEFILES這個環境變量,執行make的時候,會進行一個相似include的動做。通常不建議使用.


四、Makefile 的核心

裏主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件指示和註釋。

顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。

隱晦規則。因爲咱們的make有自動推導的功能,因此隱晦的規則可讓咱們比較粗糙地簡略地書寫Makefile,這是由make所支持的。

變量的定義。在Makefile中咱們要定義一系列的變量,變量通常都是字符串,這個有點你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。

文件指示。其包括了三個部分,一個是在一個Makefile中引用另外一個Makefile,就像C語言中的include同樣;另外一個是指根據某些狀況指定Makefile中的有效部分,就像C語言中的預編譯#if同樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在後續的部分中講述。

註釋。Makefile中只有行註釋,和UNIX的Shell腳本同樣,其註釋是用「#」字符,這個就像C/C++中的「//」同樣。若是你要在你的Makefile中使用「#」字符,能夠用反斜框進行轉義,如:「#」。


五、特殊表達式

  • 變量替換

  1. foo:=a.o b.o c.o 
  2. bar:=$(foo:.o=.c) 

理解爲bar的值等於,foo裏面的值將.o替換爲.c以後。

  • 變量再當變量

  1. x = y 
  2. y = z 
  3. a := $($(x)

這個表達式a=的值等於z;

  • 多層變量配合函數

  1. x = variable1 
  2. variable2 := Hello 
  3. y = $(subst 1,2,$(x)
  4. z = y 
  5. a := $($($(z))) 

「$($($(z)))」擴展爲「$($(y))」,而其再次被擴展爲「$($(subst 1,2,$(x)))」。$(x)的值是「variable1」,subst函數把「variable1」中的全部「1」字串替換成「2」字串,因而,「variable1」變成「variable2」,再取其值,因此,最終,$(a)的值就是$(variable2)的值—— 「Hello」。

  • 變量名組合

  1. first_second = Hello 
  2. a = first 
  3. b = second 
  4. all = $($a_$b

這裏的「$a_$b」組成了「first_second」,因而,$(all)的值就是「Hello」。

  • 「函數」和「條件語句」一同使用

  1. ifdef do_sort 
  2. func := sort 
  3. else 
  4. func := strip 
  5. endif 
  6.  
  7. bar := a d b g q
  8.  
  9. foo := $($(func) $(bar)) 

這個示例中,若是定義了「do_sort」,那麼:foo := $(sort a d b g q c),因而$(foo)的值就是「a b c d g q」,而若是沒有定義「do_sort」,那麼:foo := $(sort a d bg q c),調用的就是strip函數。

  • 某個目標的局部變量

  1. prog : CFLAGS = -g 
  2. prog : prog.o foo.o bar.o 
  3. $(CC) $(CFLAGS) prog.o foo.o bar.o 

在這個示例中,無論全局的$(CFLAGS)的值是什麼,在prog目標,以及其所引起的全部規則中(prog.o foo.o bar.o的規則),$(CFLAGS)的值都是「-g」


六、自動化變量

  • $@
    表示規則中的目標文件集。在模式規則中,若是有多個目標,那麼,"$@"就是匹配於目標中模式定義的集合。

  • $%
    僅當目標是函數庫文件中,表示規則中的目標成員名。例如,若是一個目標是"foo.a(bar.o)",那麼,"$%「就是"bar.o」,"$@「就是"foo.a」。若是目標不是函數庫文件(Unix下是
    [.a],Windows下是[.lib]),那麼,其值爲空。

  • $<
    依賴目標中的第一個目標名字。若是依賴目標是以模式(即"%")定義的,那麼"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。

  • $?
    全部比目標新的依賴目標的集合。以空格分隔。

  • $^
    全部的依賴目標的集合。以空格分隔。若是在依賴目標中有多個重複的,那個這個變量會去除重複的依賴目標,只保留一份。

  • $+
    這個變量很像"$^",也是全部依賴目標的集合。只是它不去除重複的依賴目標。

  • $*
    這個變量表示目標模式中"%「及其以前的部分。若是目標是"dir/a.foo.b」,而且目標的模式是"a.%.b",那麼,"$「的值就是"dir /a.foo」。這個變量對於構造有關聯的文件名是比較有較。
    若是目標中沒有模式的定義,那麼"$
    「也就不能被推導出,可是,若是目標文件的後綴是 make所識別的,那麼」$「就是除了後綴的那一部分。
    例如:若是目標是"foo.c」,由於".c"是make所能識別的後綴名,因此,"$
    「的值就是"foo」。這個特性是GNU make的,頗有可能不兼容於其它版本的make,因此,你應該儘可能避免使用"$「,除非是在隱含規則或是靜態模式中。若是目標中的後綴是make所不能識別的,那麼」$"就是空值。

  • $(@D)
    表示"$@「的目錄部分(不以斜槓做爲結尾),若是」$@「值是"dir/foo.o」,那麼"$(@D)「就是"dir」,而若是"$@「中沒有包含斜槓的話,其值就是」."(當前目錄)。

  • $(@F)
    表示"$@「的文件部分,若是」$@「值是"dir/foo.o」,那麼"$(@F)「就是"foo.o」,"$(@F)「至關於函數」$(notdir $@)"。

  • "$(*D)" "$(*F)"
    和上面所述的同理,也是取文件的目錄部分和文件部分。對於上面的那個例子,"$(*D)「返回"dir」,而"$(*F)「返回"foo」

  • "$(%D)" "$(%F)"
    分別表示了函數包文件成員的目錄部分和文件部分。這對於形同"archive(member)"形式的目標中的"member"中包含了不一樣的目錄頗有用。

  • "$(<D)" "$(<F)"
    分別表示依賴文件的目錄部分和文件部分。

  • "$(^D)" "$(^F)"
    分別表示全部依賴文件的目錄部分和文件部分。(無相同的)

  • "$(+D)" "$(+F)"
    分別表示全部依賴文件的目錄部分和文件部分。(能夠有相同的)

  • "$(?D)" "$(?F)"
    分別表示被更新的依賴文件的目錄部分和文件部分。

最後想提醒一下的是,對於"$<",爲了不產生沒必要要的麻煩,咱們最好給$後面的那個特定字符都加上圓括號,好比,"$(< )「就要比」$<"要好一些。

還得要注意的是,這些變量只使用在規則的命令中,並且通常都是"顯式規則"和"靜態模式規則"(參見前面"書寫規則"一章)。其在隱含規則中並無意義。

七、gcc 與 g++ 的區別

誤區一:gcc只能編譯c代碼,g++只能編譯c++代碼

二者均可以,可是請注意:

  • 1.後綴爲.c的,gcc把它看成是C程序,而g++看成是c++程序;後綴爲.cpp的,二者都會認爲是c++程序,注意,雖然c++是c的超集,可是二者對語法的要求是有區別的,例如:

  1. int main(int argc, char* argv[])
  2. if(argv == 0) return
  3. printString(argv); 
  4. return
  5. int printString(char* string)
  6. sprintf(string, "This is a test.\n"); 

若是按照C的語法規則,OK,沒問題,可是,一旦把後綴改成cpp,馬上報三個錯:

  1. 「printString未定義」; 
  2. 「cannot convert `char**' to `char*」; 
  3. return-statement with no value「; 

分別對應前面紅色標註的部分。可見C++的語法規則更加嚴謹一些。

  • 2.編譯階段,g++會調用gcc,對於c++代碼,二者是等價的,可是由於gcc命令不能自動和C++程序使用的庫聯接,因此一般用g++來完成連接,爲了統一塊兒見,乾脆編譯/連接通通用g++了,這就給人一種錯覺,好像cpp程序只能用g++似的。

誤區二:gcc不會定義__cplusplus宏,而g++會
實際上,這個宏只是標誌着編譯器將會把代碼按C仍是C++語法來解釋,如上所述,若是後綴爲.c,而且採用gcc編譯器,則該宏就是未定義的,不然,就是已定義。

誤區三:編譯只能用gcc,連接只能用g++
嚴格來講,這句話不算錯誤,可是它混淆了概念,應該這樣說:編譯能夠用gcc/g++,而連接能夠用g++或者gcc -lstdc++。由於gcc命令不能自動和C++程序使用的庫聯接,因此一般使用g++來完成聯接。但在編譯階段,g++會自動調用gcc,兩者等價。

誤區四:extern "C"與gcc/g++有關係
實際上並沒有關係,不管是gcc仍是g++,用extern "c"時,都是以C的命名方式來爲symbol命名,不然,都以c++方式命名。

八、gcc和g++的包含頭文件庫文件方法

  • -l參數 和 -L參數
    就是用來指定程序要連接的庫,-l參數緊接着就是庫名。
    -L參數跟着的是庫文件所在的目錄名

  • -include和-I參數
    -include用來包含頭文件,但通常狀況下包含頭文件都在源碼裏用#include xxxxxx實現。
    -I參數是用來指定頭文件目錄,/usr/include目錄通常是不用指定的,gcc知道去那裏找.

九、gcc中include文件的搜索路徑

#include有兩種形式,例如以下:

  1. #include <syshead.h> 
  2. #include "userhead.h" 

用尖括號表示的是包含系統的頭文件,用雙引號包含的是用戶本身的頭文件。

下面是使用#include時的一些規則:
1)使用<>包含的頭文件通常會先搜索-I選項後的路徑(即用gcc編譯時的-I選項),以後就是標準的系統頭文件路徑。
2)而用""號包含的頭文件會首先搜索當前的工做目錄,以後的搜索路徑纔是和<>號包含的頭文件所搜索的路徑同樣的路徑。
3)在unix系統中,通常標準的頭文件路徑爲:

  1. /usr/local/include 
  2. /usr/lib/gcc-lib/target/version/include 
  3. /usr/target/include 
  4. /usr/include 

4)通常有兩條獨立的頭文件搜索路徑鏈。一條是-I後面指示的路徑,另外一條是系統頭文件路徑和以-prefix, -withprefix,和-idirafter後操做的目錄。
5)若是gcc編譯的是c++的程序,那麼在搜索上面所說的目錄前,預處理器會首先搜索/usr/include/g++v3目錄,v3是你的gcc中c++的版本。
6)在頭文件中運行增長路徑名,例如:#include <sys/time.h>,那麼就會在搜索的系統目錄的sys目錄下尋找time.h文件。
7)通常會用斜線來做爲目錄的分割符,甚至有些系統使用不一樣的字符做爲分割符(例如反斜線)。

相關環境變量
有大量的環境變量可供設置以影響 GCC 編譯程序的方式。利用這些變量的控制也可以使用合適的命令行選項。一些環境變量設置在目錄名列表中。這些名字和 PATH 環境變量使用的格式相同。特殊字符 PATH_SEPARATOR (安裝編譯程序的時候定義)用在目錄名之間。在 UNIX 系統中,分隔符是冒號,而 Windows 系統中爲分號。
C_INCLUDE_PATH
編譯 C 程序時使用該環境變量。該環境變量指定一個或多個目錄名列表,查找頭文件,就好像在命令行中指定 -isystem 選項同樣。會首先查找 -isystem 指定的全部目錄。
==> 也見 CPATH 、 CPLUS_INCLUDE_PATH 和 OBJC_INCLUDE_PATH 。

COMPILER_PATH
該環境變量指定一個或多個目錄名列表,若是沒有指定 GCC_EXEC_PREFIX 定位子程序,編譯程序會在此查找它的子程序。
==> 也見 LIBRARY_PATH 、 GCC_EXEC_PREFIX 和 -B 命令行選項。

CPATH
編譯 C 、 C++ 和 Objective-C 程序時使用該環境變量。該環境變量指定一個或多個目錄名列表,查找頭文件,就好像在命令行中指定 -l 選項同樣。會首先查找 -l 指定的全部目錄。
==> 也見 C_INCLUDE_PATH 、 CPLUS_INCLUDE_PATH 和 OBJC_INCLUDE_PATH 。

CPLUS_INCLUDE_PATH
編譯 C++ 程序時使用該環境變量。該環境變量指定一個或多個目錄名列表,查找頭文件,就好像在命令行中指定 -isystem 選項同樣。會首先查找 -isystem 指定的全部目錄。
==> 也見 CPATH 、 C_INCLUDE_PATH 和 OBJC_INCLUDE_PATH 。

DEPENDENCIES_OUTPUT
爲文件名設置該環境變量會讓預處理程序將基於依賴關係的 makefile 規則寫入文件。不會包括系統頭文件名字。
若是環境變量設置爲單名,被看做是文件名字,而依賴關係規則的名字來自源文件名字。若是定義中有兩個名字,則第二個名字是用做依賴關係規則的目標名。 設置該環境變量的結果和使用命令行選項 -MM 、 -MF 和 -MT 的組合是同樣的。
==> 也見 SUNPRO_DEPENDENCIES 。

GCC_EXEC_PREFIX
若是定義了該環境變量,它會做爲編譯程序執行的全部子程序名字的前綴。例如,若是將變量設置爲 testver 而不是查找 as ,彙編器首先會在名字 testveras 下查找。若是在此沒有找到,編譯程序會繼續根據它的普通名進行查找。可在前綴名中使用斜線指出路徑名。
GCC_EXEC_PREFIX 的默認設置爲 prefix /lib/gcc-lib/ ,這裏的 prefix 是安裝編譯程序時 configure 腳本指定的名字。該前綴也用於定位標準鏈接程序文件,包含進來做爲可執行程序的一部分。
若是使用 -B 命令行選項,會重寫該設置。
==> 也見 COMPILER_PATH 。

九、gcc 常見意外處理

warning: "unused parameter xxxx"警告

  • 第一種方法

  1. #define UNUSED(x) (void)x  
  2. void SomeFunction(int param1, int param2)  
  3. {  
  4. UNUSED(param2);  
  5. // do stuff with param1  

在UNUSED(param2)語句不產生任何目標代碼,消除對未使用的變量的警告,並明確文件,不要使用變量的代碼。

  • 第二種方法
    warning: unused parameter ‘mcb’
    舉例:

  1. int ifnMenuQuit(MCB_T *mcb) 
  2. return QUIT

說明:由於函數參數中的mcb,在該函數中沒有被使用,因此產生warning
修改:對沒使用的參數使用 para=para;

  1. int ifnMenuQuit(MCB_T *mcb) 
  2. mcb=mcb; <----------添加該行 
  3. return QUIT
  • 最優美的方法

  1. #ifdef UNUSED 
  2. #elif defined(__GNUC__) 
  3. # define UNUSED(x) UNUSED_ ## x __attribute__((unused)) 
  4. #elif defined(__LCLINT__) 
  5. # define UNUSED(x) /*@unused@*/ x 
  6. #else 
  7. # define UNUSED(x) x 
  8. #endif 
  9.  
  10. void dcc_mon_siginfo_handler(int UNUSED(whatsig)) 

雜記

-fpic告訴編譯器將源代碼編譯成共享的object文件,PIC(Position-Independent Code)意思是函數都是相對地址,這是共享庫所須要的。

編譯時」-lsum「的方式,是不可以區分當前是靜態連接仍是動態連接的。若是在同一個目錄下同時有靜態連接庫和動態連接庫,則系統默認會引用動態連接庫,若是想使用靜態連接庫則須要在編譯時加上」-static「參數(具體方法可自行百度

共享庫,有兩種形式,第一種就是在上一篇文章中說到的「動態連接庫」,而共享庫的另外一種形式,則被稱之爲「動態加載庫」,也就是我剛纔提到的用「dlopen」方式來玩的。動態加載庫在編譯的時候,應該是不須要去-l引用lib,而是在可執行程序中,能夠自已決定加載庫的時機。好比程序跑着跑着,忽然想用libabc.so庫裏的一個叫abc的函數了,這時就能夠用dlopen去打開這個庫,而後使用dlsym去找到abc的函數指針並調用便可。

編譯時-rdynamic用來通知連接器將全部符號添加到動態符號表中(目的是可以經過使用 dlopen 來實現向後跟蹤)。-ldl 代表必定要將 dllib 連接於該程序

-MMD -MP -MF ./obj/local/armeabi/objs/jvm/jni-jvm.o.d
生成依賴說明文件。

-ffunction-sections, -fdata-sections會使compiler爲每一個function和data item分配獨立的section。 --gc-sections會使ld刪除沒有被使用的section。

-fstack-protector:
啓用堆棧保護,不過只爲局部變量中含有 char 數組的函數插入保護代碼。

-no-canonical-prefixes 生成其餘gcc 組件的相對路徑時不生成規範化的

-march=armv5te
-march 指定的指令集的版本 指定架構

hard-float 是直接生成浮點運算的指令(若是有的話);soft-float 是用庫模擬浮點運算(若是有的話)。

-mthumb參數是編譯全編譯成thumb指令集,-mthumb-interwork是指thumb的code用thumb指令集,其餘的用arm指令集

-fomit-frame-pointer 編譯選項
忽略幀指針。這樣在程序就不須要保存,安裝,和恢復ebp了。這樣ebp也就是一個
free的register了,在函數中就能夠隨便使用了。

-fno-strict-aliasing,則編譯器認爲任何 指針均可能指向同一個內存區域。所以對*b賦值,編譯器認爲有可能會影響a裏面的值了。因此編譯器給printf那一行傳遞參數的時候就認爲寄存器裏的 a[0],a[1]值已經不必定正確了,只有內存裏的纔可靠,因而只能老老實實從棧裏取值了。

-finline-limit=n對僞指令數超過n的函數,編譯程序將不進行內聯展開

加上GCC編譯參數–noexecstack , 用以讓程式載入後Stack會透過MMU設定不可執行. (Hardware-based No eXecute (NX)),如此可避免像是透過Stack-Overflow將程式碼注入的軟體攻擊問題.

‘-Wformat’ This option warns about the incorrect use of format strings in functions
such as printf and scanf, where the format specifier does not agree with the
type of the corresponding function argument.

因爲Android 的gdbserver 會讓程序停在linker 的啓動進程。因此通常都是顯示??,若是須要查看能夠 set sysroot adl_src/out/target/product/generic/symbols/ 或者,直接break main 下斷點,而後continue執行過去。

相關文章
相關標籤/搜索