makefile從入門到放棄——博主吐血整理的筆記

       想要成爲專業程序員,mekefile必須懂 !尤其是在Linux下進行軟件編譯,makefile就不得不自己寫。因爲,makefile關係到了整個工程的編譯規則。一個工程中的源文件不計其數,並且按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要後編譯,哪些文件需要重新編譯,甚至於進行更復雜的功能操作,因爲makefile就像一個 Shell腳本一樣,其中也可以執行操作系統的命令。
       makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟件開發的效率。 make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數的IDE都有這個命令,比如:Visual C++的nmake,Linux下GNU的make。可見,makefile都成爲了一種在工程方面的編譯方法。
       這篇文章需要C/C++編譯、Linux基礎、GUN工具使用的相關知識,可以借鑑博主往期文章有詳細講解!

往期文章傳送門:
       史上最全的Linux常用命令彙總(超全面!超詳細!收藏這一片就夠了)
       GUN工具的使用(gcc實現代碼從編譯到運行的詳細介紹)
       Shell基礎學習筆記——文章最後送有Shell學習的電子書

make簡介

  • 工程管理器,顧名思義,指管理較多的文件。
  • Make工程管理器就是個“自動化編譯管理器”,這裏的“自動”是指它能夠根據文件時間戳自動發現更新過的文件而減少編譯的工作量,同時,它通過讀入Makefile文件的內容來執行大量的編譯工作。
  • Make將只編譯改動的代碼文件,而不用完全編譯。

makefile基本結構

makefile是make讀入的唯一配置文件

  • 由make工具創建的目標體(target),通常是目標文件或可執行文件
  • 要創建的目標體所依賴的文件(dependency_file)
  • 創建每個目標體時需要運行的命令(command)
  • 注意:命令前必須是一個“TAB鍵”,否則編譯錯誤爲:*** missing separator. Stop.

makefile格式:

target : dependency_files ##dependency_files是依賴的文件
	command #注意是一個TAB

       例子:

hello.o:hello.c hello.h
	gcc -c hello.c -o hello.o

makefile的基本使用實例

新建f1.c、f2.c、main.c、head.h四個文件,並輸入一下內容:

  • f1.c內容如下:
#include <stdio.h>

void print1(){
    printf("Message:f1.c\n");
}
  • f2.c內容如下:
#include <stdio.h>

void print2()
{
    printf("Message:f2.c\n");
}
  • head.h內容如下:
void print1();
void print2();
  • main.c內容如下:
#include <stdio.h>
#include "head.h"#調用自己寫的頭文件用引號

int main()
{
        print1();
        print2();

        printf("end main\n");
        return 0;
}

       在沒有編寫makefile之前可以使用命令gcc *.c -Wall查看當前代碼是否有語法錯誤。檢查沒有語法錯誤以後可以編寫makefile文件。

  •        新建makefile文件並輸入以下內容:
test:f1.o f2.o main.o
    gcc f1.o f2.o main.o -o test
f2.o:f2.c
    gcc -c -Wall f2.c -o f2.o# -Wall允許發出gcc所有有用的報警信息
f1.o:f1.c
    gcc -c -Wall f1.c -o f1.o#-c表示只編譯不鏈接,生成目標文件“.o”
main.o:main.c
    gcc -c -Wall main.c -o main.o#-o file表示把輸出文件輸入到file裏
clean:
	rm *.o test#刪除.o和執行文件

       執行makefile文件:

$ make #默認生成第一個文件
$ make '目標名'#選擇性的編譯

       當工程中的文件名和makefile中的目標重名時,就會有僞目標。執行make命令時會發現提示目標文件已經是最新的了,將不被不執行!如果我想讓makefile中某個命令永遠被執行。可以在makefile目標前加上.PHONY:'目標名'

makefile變量

       在makefile中的定義的變量,就像是C/C++語言中的宏一樣,他代表了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。其與C/C++所不同的是,你可以在Makefile中改變其值。在makefile中,變量可以使用在“目標”,“依賴目標”, “命令”或是Makefile的其它部分中。
       變量的命名字可以包含字符、數字,下劃線(可以是數字開頭),但不應該含有“:”、“#”、“=”或是空字符(空格、回車等)。變量是區分英文字母大小寫的。

創建和使用變量:

  • 變量的類型

       預定義變量:

變量名 變量含義
- AR 庫文件維護程序名稱,默認爲ar.AS彙編程序名稱,默認值爲as。
- CC C編譯器的名稱,默認爲cc。CPP C預編譯器的名稱,默認值爲$(CC) -E
- CXX C++編譯器的名稱,默認爲g++
- FC FORTRAN編譯器的縮寫,默認值爲f77
- RM 文件刪除程序名稱,默認爲rm -f

       自動變量:

變量名 變量含義
- $* 不包含擴展名的目標文件名稱
- $+ 所有的依賴文件,以空格分開,並以出現後的先後爲序,可能包含重複依賴文件
- $< 第一個依賴文件的名稱
- $? 所有時間戳比目標文件晚的依賴文件,並以空格分開目標文件的完整名稱
- $@ 目標文件的完整名稱
- $^ 所有不重複的目標依賴文件,以空格分開
- $% 如果目標是歸檔成員,則該變量表示目標的歸檔成員的目標名稱
  • 變量定義的兩種方式
- 遞歸展開方式 VAR=var
- 簡單方式 VAR: =var

       一般使用遞歸展開方式進行變量定義

  • 變量的使用

       示例1:

dir :=/foo/bar
FOO?=bar

       以上代碼的含義是:如果FOO沒有定義過,那麼變量FOO的值就是‘bar’,如果FOO之前被定義過,那麼什麼也不做。
       示例2:

#爲變量添加值:可以通過+=爲己定義的變量添加新的值
Main=hello.o hello-1.o
Main+=hello-2.o

make的使用

  • 直接運行make
  • 選項
參數 參數的作用
-C dir讀入指定目錄下的makefile
-f file讀入當前目錄下的file文件作爲makefile
-i 忽略所有命令執行錯誤
-I dir制定被包含的makefile所在目錄
-n 只打印要執行的命令,但是不執行這些命令
-p 顯示make變量數據庫的隱含規則
-s 在執行命令時不顯示命令
-w 如果執行make在執行過程中改變目錄,打印當前目錄名

當然makefile也可以像C語言一樣調用其他的makefile的文件

       config.mk文件內容如下:

OBJS=f1.o f2.o
OBJS+=main.o
CFLAGS=-c -Wall -I include

       makefile文件內容如下:

include config.mk#調用config.mk文件的內容
test:$(OBJS)
	gcc $(OBJS) -o test
f2.o:$<
	gcc $(CFLAGS) f2.c -o $@
f1.o:f1.c
	gcc $(CFLAGS) f1.c -o $@
main.o:main.c
	gcc $(CFLAGS) main.c -o mian.o
.PHONY:clean
clean:
	rm *.o test

makefile的隱含規則

  • 隱含規則1:編譯C語言的隱含規則
    .o的目標的依賴目標會自動推導爲.c,並且其生成命令是$(CC) -c $(CPPFLAGS) $(CFLAGS)
    那麼makefile可以簡寫——去掉.o到.c的依賴也可以,那麼makefile內容可以簡化如下:
include config.mk
test:$(OBJS)
	gcc $(OBJS) -o test

.PHONY:clean
clean:
	rm *.o test
  • 隱含規則2:鏈接Object文件的隱含規則
    n目標依賴於n.o,通過運行C語言編譯器來運行鏈接程序生成(一般是“ld”),其命令是:$(CC) $(LDFLAGS) n.o
x : x.o y.o#並且x.c、y.c都存在時,隱含命令如下
cc -c x.c -o x.o
cc -c y.c -o y.o
cc x.o y.o -o x

       注意如果沒有一個源文件(如上例中的x.c)和目標名字(如上例中的x)相關聯,那麼最好寫出自己的生成規則,不然,隱含規則會報錯。

       那麼根據此隱含規則,我們又可以將makefile文件進行優化爲以下內容:

include config.mk
f1:f1.o f2.o main.o

.PHONY:clean
clean:
	rm *.o test

第二行代碼中就是應用了規則二,目標文件可以改成f2、main都可以,但是必須是和所依賴的文件相關聯,如果不關聯(如:test)那麼makefile就會報錯。

makefile的VPATH

VPATH:虛路徑

  • 在一些大的工程中,有大量的源文件,我們通常的做法是把許多的源文件分類,並且存放在不同的目錄下。所以,當make需要找文件依賴關係時,可以在文件前加上路徑,最好的辦法就是把一個路徑告訴make,讓make在自動的去找。
  • makefile文件中的特殊變量VPATH就是完成這麼一個功能,如果沒有指明這個變量,make只會在當前目錄中去尋找依賴文件和目標文件。如果定義了這個變量,那麼,make就會在當前目錄找不到的情況下,到指定的目錄中去找尋文件了。
  • VPATH = src:../headers
  • 上面的定義指定兩個目錄,‘src’和‘…/headers’,make會按照這個順序進行搜索。目錄由‘冒號’分割。當然,當前目錄永遠是最高優先級搜索的地方

示例:
新建src1/f1.c、src2/f2.c、main/main.c、include/head.h、include/myinclude.h四個文件,並輸入一下內容:

  • src1/f1.c內容如下:
#include <stdio.h>

void print1(){
    printf("Message:f1.c\n");
}
  • src2/f2.c內容如下:
#include <stdio.h>

void print2()
{
    printf("Message:f2.c\n");
}
  • include/head.h內容如下:
void print1();
void print2();
  • include/myinclude.h內容如下:
#include <stdio.h>
  • main/main.c內容如下:
#include <stdio.h>
#include "head.h"#調用自己寫的頭文件用引號

int main()
{
        print1();
        print2();

        printf("end main\n");
        return 0;
}

沒有使用VPATH的的makefile內容如下:

CFLAGS=-c -Wall -I include
test:src1/f1.o src2/f2.o main/main.o
	gcc src1/f1.o src2/f2.o main/main.o -o test
src1/f1.o:src1/f1.c#可以省略
	gcc $(CFLAGS) $^ -o $@
src2/f2.o:src2/f2.c
	gcc $(CFLAGS) $^ -o $@
main/main.o:main/main.c
	gcc $(CFLAGS) $^ -o $@#可以省略	
.PHONY:clean
clean:
	find ./ -name "*.o" -exec rm {} \;;test#刪除當前目錄下所有的.o文件

以上代碼中根據隱含規則可以簡化——第4行~第9行可以省略

利用VPATH進行優化makefile

CFLAGS=-c -Wall -I include
VPATH=src1 src2 main
f1:src1/f1.o src2/f2.o main/main.o

.PHONY:clean
clean:
	find ./ -name "*.o" -exec rm {} \;;test

makefile的嵌套

       當工程文件比較多時,如果只使用一個makefile會使得這個makefile顯得比較繁瑣複雜,那麼我們可以通過使用makefile嵌套,內層使用子makefile,外層來調用這些子makefile。
如何來使用呢?舉例說明如下:

subsystem:
    cd subdir && $(MAKE)

       這個例子可以這樣來理解,在當前目錄下有一個目錄文件 subdir 和一個 makefile 文件,子目錄 subdir 文件下還有一個 makefile 文件,這個文件是用來描述這個子目錄文件的編譯規則。使用時只需要在最外層的目錄中執行 make 命令,當命令執行到上述的規則時,程序會進入到子目錄中執行 make。這就是嵌套執行 make,我們把最外層的 Makefile 稱爲是總控 makefile。

上述的規則也可以換成另外一種寫法:

subsystem
    $(MAKE) -C subdir

       在 make 的嵌套執行中,我們需要了解一個變量 “CURDIR”,此變量代表 make 的工作目錄。當使用 make 的選項 “-C” 的時候,命令就會進入指定的目錄中,然後此變量就會被重新賦值。總之,如果在 Makefile 中沒有對此變量進行顯式的賦值操作,那麼它就表示 make 的工作目錄。我們也可以在 Makefile 中爲這個變量賦一個新的值,當然重新賦值後這個變量將不再代表 make 的工作目錄。
export的使用:
       使用 make 嵌套執行的時候,變量是否傳遞也是我們需要注意的。如果需要變量的傳遞,那麼可以這樣來使用:

export <variable>

如果不需要那麼可以這樣來寫:

unexport <variable>

       <variable>是變量的名字,不需要使用 “$” 這個字符。如果所有的變量都需要傳遞,那麼只需要使用 “export” 就可以,不需要添加變量的名字。

       Makefile 中還有兩個變量不管是不是使用關鍵字 “export” 聲明,它們總會傳遞到下層的 Makefile 中。這兩個變量分別是 SHELL 和 MAKEFLAGS,特別是 MAKEFLAGS 變量,包含了 make 的參數信息。如果執行總控 Makefile 時,make 命令帶有參數或者在上層的 Makefile 中定義了這個變量,那麼 MAKEFLAGS 變量的值將會是 make 命令傳遞的參數,並且會傳遞到下層的 Makefile 中,這是一個系統級別的環境變量。

       make 命令中有幾個參數選項並不傳遞,它們是:"-C"、"-f"、"-o"、"-h" 和 “-W”。如果我們不想傳遞 MAKEFLAGS 變量的值,在 Makefile 中可以這樣來寫:

subsystem:
    cd subdir && $(MAKE) MAKEFLAGS=

       不積小流無以成江河,不積跬步無以至千里。而我想要成爲萬里羊,就必須堅持學習來獲取更多知識,用知識來改變命運,用博客見證成長,用行動證明我在努力。
       如果我的博客對你有幫助、如果你喜歡我的博客內容,請“點贊” “評論” “收藏”一鍵三連哦!聽說點讚的人運氣不會太差,每一天都會元氣滿滿呦!如果實在要白嫖的話,那祝你開心每一天,歡迎常來我博客看看。

在這裏插入圖片描述

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