1. 程式人生 > >簡介make命令和makefile檔案

簡介make命令和makefile檔案

一、為什麼要用到 make 命令和 makefile 檔案

  在 Linux 下編寫一個程式,每次編譯都需要在命令列一行一行的敲命令。如果是一個很小的程式還好說,命令不怎的複雜,編譯速度也挺快,但是對於大型程式來說,這樣無疑很麻煩,且不說可能會敲錯命令,有時候僅僅改動了一個小地方,卻需要將整個程式全部重新編譯一遍,顯然很浪費時間。Linux 提供了 make 命令來解決上述問題,它會在必要時重新編譯所有受改動影響的原始檔。同時,還提供了一個 makefile 檔案,它告訴 make 命令如何構建應用程式。這裡用一個簡單的例子提前演示一下:

/* hello.c */
#include <unistd.h> #include
<stdio.h> #include <stdlib.h> int main() { printf("hello world!\n"); exit(0); }
/* Makefile */
hello: hello.c gcc
-o hello.s -S hello.c gcc -o hello.o -c hello.s gcc -o hello hello.o clean: -rm hello hello.s hello.o

  這裡提供了兩段程式碼,第一段程式碼是一個簡單的 HelloWorld 程式,第二段程式碼是為這個程式編寫的一個 makefile 檔案。此時,只需要在命令列輸入 make 命令,就可以對原始檔 hello.c 進行編譯,如下:

  執行 make 命令時,make 命令會讀取 makefile 檔案,並按照 makefile 檔案中給出的命令來建立檔案,同時會在執行時將命令列印到標準輸出。執行完 make 命令後,原始檔所在目錄下多了三個檔案:hello、hello.o 和 hello.s,其中 hello 是可執行檔案,使用命令 ./hello 即可檢視程式的輸出結果。這和直接在命令列使用 gcc 命令所得到的結果是一樣的,而且,當你修改了原始檔時,也只需要再次使用 make 命令即可重新編譯,十分方便。 

 

二、make 命令

  make 命令用於從一個名為 makefile 的檔案中獲得構建一個程式的依賴關係。make 命令會根據 makefile 檔案來確定目標檔案的建立順序以及正確的規則呼叫順序。

make 命令的一些常用引數

1)-k 引數:

  使用 -k 引數可以讓 make 命令在發現錯誤時仍然繼續執行,而不是在檢測到第一個錯誤時就停下來。利用這個選項可以在一次操作中發現所有未編譯成功的原始檔;

2)-n 引數:

   使用 -n 引數,讓 make 命令輸出將要執行的操作步驟,而不是真正執行這些操作;

3)-f 引數:

  使用 -f 引數,後面可以接一個檔名,用於指定一個檔案作為 makefile 檔案。如果沒有使用 -f 選項,則 make 命令會在當前目錄下查詢名為 makefile 的檔案,如果該檔案不存在,則查詢名為 Makefile 的檔案。 

 

三、makefile 檔案

  makefile 檔案由一組依賴關係規則構成。每個依賴關係都由一個目標(即將要建立的檔案)和一個該目標所依賴的原始檔組成;規則描述瞭如何通過這些依賴檔案建立目標。簡單的來說,makefile 檔案的寫法如下:

target: prerequisites
    command1
    command2
    ...

  其中,target 是即將要建立的目標(通常是一個可執行檔案),target 後面緊跟一個冒號,prerequisite 是生成該目標所需要的原始檔(依賴),一個目標所依賴的檔案可以有多個,依賴檔案與目標之間以及各依賴檔案之間用空格或製表符 Tab 隔開,這些元素組成了一個依賴關係。隨後的命令 command 就是規則,也就是 make 需要執行的命令,它可以是任意的 shell 命令。另外,makefile 檔案中,註釋以 # 號開頭,一直延續到該行的結束

3.1 依賴關係

  依賴關係定義了最終應用程式裡的每個檔案與原始檔之間的關係。一個依賴關係列表由目標和該目標的零個或多個依賴組成,語法是:先寫目標,然後接一個冒號,再用一個空格或製表符隔開,最後是用空格或製表符隔開的依賴檔案列表,如下:

target: prerequisite1 prerequisite2 prerequisite3 ...

  依賴關係表明了這樣一件事:目標檔案 target 依賴於檔案 prerequisite1、prerequisite2、prerequisite3 ...,即,要生成 target,需要有這幾個依賴檔案的存在,而且,若其中一個依賴檔案發生了改變,則需要重新生成 target。目標所依賴的檔案可以有一個或多個,也可以沒有依賴檔案 —— 該目標總被認為是過時的,在執行 make 命令時,若指定了該目標,則該目標所對應的規則將總被執行(如目標 clean)。

  makefile 檔案中可以有很多個目標,每個目標都有自己對應的規則。make 命令預設建立的是 makefile 檔案中的第一個目標。也可以自己指定一個目標讓 make 命令去建立,只需要將該目標的名字作為引數放到 make 命令之後即可(如常用的 make clean)。實際上,更好的做法是,將 makefile 檔案中的第一個目標定義為 all,然後再 all 後面列出其他從屬目標,這將告訴 make 命令,在未指定特定目標時,預設情況下將建立哪個目標。此外,使用目標 all ,還可以使 make 命令一次性建立多個檔案,這取決於 all 後面所接的從屬目標的個數。

  舉個例子說明一下檔案與檔案之間的依賴關係:

/* sum.c */
#include <stdio.h>
#include <stdlib.h>

extern int add(int i,int j);

int main()
{
  printf("%d\n",add(1,2));
  exit(0);
}

/* add.c */
#include <stdio.h>

int add(int i,int j)
{
  int k;
  k = i + j;
  return k;
}

  這是一個簡單的加法程式,包含兩個檔案:sum.c 和 add.c,其中,sum.c 中的 main 函式呼叫了 add.c 中的 add 函式。這個程式的依賴關係表如下:

sum: sum.o add.o
sum.o: sum.c stdio.h stdlib.h
add.o: add.c stdio.h

  其中,最終所需要的目標檔案是 sum,sum.o 和 add.o 是依賴 —— 要生成目標檔案 sum ,需要先生成 sum.o 和 add.o。同樣的,作為目標的 sum.o 依賴於 sum.c、stdio.h 和 stdlib.h;add.o 依賴於 add.c 和 stdio.h。這組依賴關係形成了一個層次結構,它顯示了原始檔之間的關係。

  可以看出來,如果 add.c 發生了改變,那麼就需要重新編譯 add.o,而由於 add.o 發生了改變,目標檔案 sum 也需要被重新建立,同時,由於 add.c 的改變並沒有影響到 sum.o(sum.o 不依賴於 add.c),因此,sum.o 並不需要被重新編譯。也就是說,通過使用 makefile 檔案和 make 命令,我們可以實現,只重新編譯所有受到改動影響的原始檔,沒有受到影響的原始檔不必重新編譯。這比把整個程式全部重新編譯一遍顯然要快上很多,尤其是對於大型程式。

 

3.2 規則 

  makefile 檔案裡另一部分內容是規則,它們定義了目標的建立方式。 規則的內容可以是任意的 shell 命令。關於規則,有以下兩點需要注意:

1)規則所在行必須以製表符 tab 開頭,不能用空格

2)規則所在行最好不要以空格結尾,可能會導致 make 命令執行失敗;

3)如果一行不足以寫下所有內容,需要在每行程式碼的結尾加上一個反斜槓符 “\”,以讓所有的命令在邏輯上處於同一行。

兩個特殊字元 - 和 @:

  1)在規則中,若命令之前加上了符號 “-”,則表明 make 命令將忽略該命令產生的所有錯誤;

  2)若在命令之前加上了符號“@”,則表明 make 在執行該命令前,不會將該命令顯示在標準輸出上。

/* Makefile */
all: sum sum: sum.o add.o gcc
-o sum add.o sum.o sum.o: sum.c gcc -c sum.c add.o: add.c gcc -c add.c
clean:
-rm sum sum.o add.o

  這是 3.1 中 sum.c 程式的 makefile 檔案。其中 gcc 、rm 命令等行就是規則,它們告訴了 make 命令將如何去建立目標。

兩個特殊的目標:clean 和 install 

  目標 clean 和 install 是兩個特殊的目標,它們並不用於建立檔案,而是有其他用途。

  目標 clean 在前面已經提到過,它使用 rm 命令來刪除目標檔案。rm 命令通常以減號 - 開頭,表示讓 make 命令忽略該命令的執行結果,這意味著,即使由於檔案不存在而導致 rm 命令返回錯誤,命令 make clean 也能成功執行。

  目標 install 用於按照命令的執行順序將應用程式安裝到指定的目錄,還是用上面的 sum.c 程式來演示一下目標 install 的用法:

all: sum

# 安裝目錄
INSTDIR = /tmp

sum: sum.o add.o
    gcc -o sum add.o sum.o
sum.o: sum.c
    gcc -c sum.c
add.o: add.c
    gcc -c add.c
clean:
    rm sum sum.o add.o

install: sum
    @if [ -d $(INSTDIR) ];\
    then\
            cp sum $(INSTDIR);\
            chmod a+x $(INSTDIR)/sum;\
            chmod og-w $(INSTDIR)/sum;\
            echo "Installed in $(INSTDIR)";\
    else\
            echo "The directory $(INSTDIR) dose not exist!";\
    fi

  使用這個 makefile 檔案,make 命令將會把 sum 安裝到目錄 /tmp 下(實際上,應用程式一般是安裝在 /usr/local/bin 下的,這裡為了方便就放到 /tmp 下了) 。執行 make install 命令,將得到如下結果:

  輸出結果顯示 sum 已被成功安裝到了 /tmp 目錄下(實際上就是把可執行檔案 sum 複製到 /tmp 目錄下)。再進入 /tmp 目錄檢視,可以看到可執行檔案 sum,其檔案許可權是 rwxr-xr-x,與 makefile 檔案中所設定的一致。

 

3.3 makefile 檔案中的巨集 

  在 makefile 檔案中定義一個巨集很簡單,如下:

MACRONAME=value

  這裡定義了一個巨集 MACRONAME,引用巨集的方法是使用 $(MACRONAME) 或 ${MACRONAME} 。使用巨集定義,可以讓 makefile 檔案的可移植性更強。除了自己定義一些巨集以外,make 命令還內建了一些特殊的巨集定義,使得 makefile 檔案變得更加簡潔:

         巨集           說明
$? 當前目標所依賴的檔案列表中比當前目標檔案還要新的檔案
[email protected] 當前目標的名字
$< 當前依賴檔案的名字
$* 不包括字尾名的當前依賴檔案的名字

  除了在 makefile 檔案裡面定義巨集以外,還可以呼叫 make 命令時,在命令列上給出巨集定義。命令列上的巨集定義將 覆蓋在 makefile 檔案中的巨集定義。需要注意的是,在 make 命令後接巨集定義時,巨集定義必須以單個引數的形式傳遞,因此,需要避免在巨集定義中使用空格或加引號。 

 

參考資料:

《Linux 程式設計 第四版》

 https://www.ibm.com/support/knowledgecenter/zh/ssw_aix_71/com.ibm.aix.cmds3/make.htm