要學好 C 語言 / C++ ,Makefile 可少不了

1、Makefile 簡介

1. Makefile 是什麼?

Makefile 一般指的是一個含有一系列命令(directive)的,經過 Make 自動化編譯工具,幫助 C/C++ 程序實現自動編譯目標文件的文件。這個文件的默認命名是 "Makefile"。html

2. 爲何要使用 Makefile?

Makefile 文件描述了整個工程的編譯、連接的規則。linux

爲工程編寫 Makefile 的好處是可以使用一行命令來完成「自動化編譯」。只需提供一個(一般對於一個工程來講會是多個)正確的 Makefile,接下來每次的編譯都只須要在終端輸入「make」命令,整個工程便會徹底自動編譯,極大提升了效率。尤爲是在編譯一個僅有一小部分文件被改動過的大項目的狀況下。函數

絕大多數的 IDE 開發環境都會爲用戶自動編寫 Makefile。工具

3. Make 是怎麼工做的?

Make 工做的原則就是:學習

一個目標文件當且僅當在其依賴文件(dependencies)的更改時間戳比該目標文件的建立時間戳新時,這個目標文件才須要被從新編譯。測試

Make 工具會遍歷全部的依賴文件,而且把它們對應的目標文件進行更新。編譯的命令和這些目標文件及它們對應的依賴文件的關係則所有儲存在 Makefile 中。3d

Makefile 中也指定了應該如何建立,建立出怎麼樣的目標文件和可執行文件等信息。code

除此以外,你甚至還能夠在 Makefile 中儲存一些你想調用的系統終端的命令,像一個 Shell 腳本同樣使用它。htm

2、簡單瞭解編譯鏈接與執行

1. 實驗介紹

按照 GNU make 官方手冊中採用的教學模式,在正式的學習 Makefile 知識以前,本次實驗先介紹一些簡單的前導知識。實驗詳細介紹了 GNU GCC 編譯和連接的基本方法,經過編譯、連接、靜態連接、動態連接的實驗內容讓用戶學習和理解 GCC 的基本使用方法。同時,用戶也將在實驗過程當中體會到手動編譯連接的低效,從而體會到自動編譯的在項目工程管理中的重要性。對象

知識點

  • GCC 編譯的使用方式
  • GCC 連接的使用方式
  • GCC 靜態連接的使用方式
  • GCC 動態連接的使用方式
  • GCC 靜態連接 + 動態連接混用的方式

代碼獲取

經過在 Terminal 中輸入如下命令能夠將本課程所涉及到的全部源代碼下載到在線環境中,做爲參照對比進行學習。

wget http://labfile.oss.aliyuncs.com/courses/849/make_example-master.zip && unzip make_example-master.zip && rm make_example-master.zip

命令執行後 WebIDE 的工做區中將會出現一個名爲 make_example-master 的文件夾,文件夾中包含了課程所涉及到的源代碼,目錄結構如圖所示:

2. 實驗步驟

本章節的源代碼位於 /home/project/make_example-master/chapter0 目錄中,請在 Terminal 中經過 cd 命令切換至該目錄後再進行實驗學習。

項目涉及到的代碼文件:
(1)main.c: 主要文件
(2)add_minus.c add_minus.h: 加減法 API 及實現
(3)multi_div.c multi_div.h: 乘除法 API 及實現

項目涉及到的 gcc 參數:

參數 描述
-c 編譯、彙編指定的源文件(也就是編譯源文件),可是不進行連接
-o 用來指定輸出文件
-L 爲 gcc 增長一個搜索連接庫的目錄
-l 用來指定程序要連接的庫

這一章節咱們將正式開始進行簡易四則預算程序的編譯實驗,分步驟進行。

主程序的編譯、連接與執行

打開 chapter0 文件夾查看 main.c 文件,內容以下:

#include <stdio.h>

int main(void)
{
    printf("Hello Cacu!\n");
    return 0;
}

點擊 chapter0 文件夾並右鍵選擇 Open in Terminal 在終端中打開 main.c 所在的文件夾。

在 Terminal 中執行如下命令,對 main.c 文件只編譯而不連接。

gcc -c main.c

能夠發如今當前目錄中生成了一個新的文件 main.o

經過 file 命令查看 main.o 的文件格式:

file main.o

輸出結果如圖所示:

這說明 main.o 其實是一個 relocatable object 文件。

經過如下命令爲 main.o 文件賦予可執行的權限:

chmod 777 main.o

chmod 命令用於改變文件的讀寫以及運行許可設置,詳細解紹參考 Permissions

輸入如下命令嘗試執行 main.o 文件:

./main.o

Terminal 輸出可執行文件格式錯誤,如圖所示:

說明 relocatable object 文件是不可執行的。

接下來經過 GCC 對 main.o 文件進行連接操做,從而生成一個可執行的程序 main

在 Terminal 中輸入如下命令將 main.o 連接爲 main 文件:

gcc -o main main.o

能夠發現當前目錄新增了一個名爲 main 的文件。

經過 file 命令查看 main 的文件格式:

file main

輸出結果如圖所示:

說明 main 文件是一個可執行的文件,因而經過如下命令來執行 main 文件:

./main

輸出結果如圖所示:

說明程序獲得了正確的執行。

靜態連接

編寫 add_minus.h 文件,在文件中對函數 add()minus() 進行聲明,不過在 chapter0 文件夾中已經提供編寫好的 add_minus.h 文件,咱們能夠拿來直接使用。

文件內容以下:

#ifndef __ADD_MINUS_H__
#define __ADD_MINUS_H__

int add(int a, int b);
int minus(int a, int b);

#endif /*__ADD_MINUS_H__*

編寫 add_minus.c 文件,實現函數 add()minus() ,一樣的在 chapter0 文件夾中已經有編寫好的 add_minus.c 文件,咱們能夠拿來直接使用。

文件內容以下:

#include "add_minus.h"

int add(int a, int b)
{
    return a+b;
}

int minus(int a, int b)
{
    return a-b;

add_minus.c 文件進行編譯,生成 add_minus.o 文件。

gcc -c add_minus.c

修改 main.c 文件,爲其增長加減法運算並編譯這個文件。

執行如下命令給 main.c 打上 v1.0.patch 補丁:

patch -p2 < v1.0.patch

patch 命令能夠處理 diff 程序生成的補丁文件,補丁格式能夠是四種比較格式中任意一種, 而後把這些差別融入到原始文件中,生成一個打過補丁的版本。-p 選項表示剝離層級,經過在 Terminal 中輸入 man patch 命令可獲取詳細說明。

此時 main.c 文件內容以下:

#include <stdio.h>
#include "add_minus.h"

int main(void)
{
    int rst;

    printf("Hello Cacu!\n");

    rst = add(3,2);
    printf("3 + 2 = %d\n",rst);

    rst = minus(3,2);
    printf("3 - 2 = %d\n",rst);

    return

經過如下命令對 main.c 文件進行編譯和連接:

gcc -c main.c
gcc -o main main.o

連接生成的 main.o 文件時,發現有錯誤出現,錯誤內容如圖所示:

緣由在於連接過程當中找不到 addminus 這兩個 symbol。

現將 main.oadd_minus.o 連接成可執行文件並執行測試。

gcc -o main main.o add_minus.o

執行新生成的可執行文件 main

./main

輸出結果以下:
說明程序獲得了正常執行。

從新編譯 add_minus.c 生成 add_minus.o 文件。

gcc -c add_minus.c

經過 ar 命令將 add_minus.o 打包到靜態庫中。

ar rc libadd_minus.a add_minus.o

能夠發如今當前目錄下,生成了一個名爲 libadd_minus.a 的靜態庫文件。
file 命令查看 libadd_minus.a 的文件格式。

file libadd_minus.a

Terminal 輸出結果如圖所示:
實際上 libxxx.a 格式的文件能夠簡單的當作指定的以 .o 結尾的文件集合。

連接 main.o 和靜態庫文件。

gcc -o main2 main.o -L./ -ladd_minus

-L./:代表庫文件位置在當前文件夾。
-ladd_minus:表示連接 libadd_minus.a 文件,使用 -l 參數時,前綴 lib 和後綴 .a 是須要省略的。

執行 main2

./main2

Terminal 輸出結果如圖所示:
說明程序的到了正確的執行。

動態連接

編寫 multi_div.h 文件,並在其中對函數 multi()div() 進行聲明。因爲提供的源代碼中已經包含了編寫好的 multi_div.h 文件,所以咱們能夠拿來直接使用。

multi_div.h 文件的內容以下:

#ifndef __MULTI_DIV_H__
#define __MULTI_DIV_H__

int multi(int a, int b);
int div(int a, int b);

#endif /*__MULTI_DIV_H__*

編寫 multi_div.c 文件,實現函數 multi()div(),一樣的因爲提供的源代碼中已包含了編寫好的 multi_div.c 文件,咱們能夠直接拿來使用。

multi_div.c 文件內容以下:

#include "multi_div.h"

int multi(int a, int b)
{
    return a*b;
}

int div(int a, int b)
{
    return a/b;

經過如下命令將 multi_div.c 文件編譯成動態連接庫。

gcc multi_div.c -fPIC -shared -o libmulti_div.so

-fPIC 選項做用於編譯階段,在生成目標文件時就得使用該選項,以生成位置無關的代碼。

命令執行結束後,在當前目錄下會生成一個名爲 libmulti_div.so 的文件。
經過 file 命令來查看 libmulti_div.so 的文件格式。

file libmulti_div.so

Terminal 輸出結果如圖所示:
由此可知 libmulti_div.so 是一個 shared object 文件。

刪除以前的 main.c 文件,並編寫新的 main.c 文件,內容以下:

#include <stdio.h>

int main(void)
{
    printf("Hello Cacu!\n");
    return 0;
}

經過如下命令爲 main.c 打上 v2.0.patch 補丁:

patch -p2 < v2.0.patch

此時 main.c 文件的內容以下:

#include <stdio.h>
/*
#include "add_minus.h"
*/
#include "multi_div.h"

int main(void)
{
    int rst;

    printf("Hello Cacu!\n");
/*
    rst = add(3,2);
    printf("3 + 2 = %d\n",rst);

    rst = minus(3,2);
    printf("3 - 2 = %d\n",rst);
*/
    rst = multi(3,2);
    printf("3 * 2 = %d\n",rst);

    rst = div(6,2);
    printf("6 / 2 = %d\n",rst);

    return

編譯 main.c 生成 main.o

gcc -c main.c

連接 main.o 與動態連接庫文件。

gcc -o main3 main.o -L./ -lmulti_div

執行生成的 main3 文件。

./main3

輸出結果出現錯誤,如圖所示:
出現錯誤的緣由是咱們生成的動態庫 libmulti_div.so 並不在庫文件搜索路徑中。

解決辦法:

  1. libmulti_div.so 拷貝到 /lib//usr/lib/ 文件夾下。
sudo cp libmulti_div.so /usr/lib
  1. LD_LIBRARY_PATH 變量中指定庫文件路徑,而動態連接庫文件存放在 /home/project/make_example-master/chapter0/ 路徑下。

因此須要在 Terminal 中執行下面的命令:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/project/make_example-master/chapter0/

如今在 Terminal 中執行下面的命令:

./main3

輸出結果如圖所示:
說明程序獲得了正確的執行。

混合使用靜態連接與動態連接

刪除舊的 main.c 文件,並編寫新的 main.c 文件,內容以下:

#include <stdio.h>

int main(void)
{
    printf("Hello Cacu!\n");
    return 0;
}

爲新的 main.c 文件打上 v3.0.patch 補丁。

patch -p2 < v3.0.patch

編譯 main.c 生成 main.o

gcc -c main.c

測試執行混用靜態連接和動態連接的方式。

gcc -o main4 main.o -L./ -ladd_minus -lmulti_div

因爲咱們以前已經修改過 LD_LIBRARY_PATH 變量,因此這次無需再次修改。

執行下面的命令:

./main4

輸出結果如圖所示:
說明程序獲得正確的執行。

儘管咱們知道不管是靜態連接仍是動態連接都能達到連接對象文件生成可執行文件的目的,可是咱們仍是得 z 注意靜態連接庫與動態連接庫之間的區別,詳細內容參考 Static, Shared Dynamic and Loadable Linux Libraries

3、總結

上述內容來自課程《Makefile 基礎入門實戰》,主要介紹了 GCC 編譯,連接的方法和靜態連接庫與動態連接庫的建立和使用方法。

後續課程內容將學習如下內容:

點擊《Makefile 基礎入門實戰》,便可可學習完整課程!

相關文章
相關標籤/搜索