makefile

摘自:http://blog.csdn.net/kesaihao862/article/details/7332528程序員

-------------------------編程

這篇文章介紹在LINUX下進行C語言編程所須要的基礎知識。在這篇文章當中,咱們將會學到如下內容:函數

  1. 源程序編譯
  2. Makefile的編寫
  3. 程序庫的連接
  4. 程序的調試
  5. 頭文件和系統求助

1.源程序的編譯

在Linux下面,若是要編譯一個C語言源程序,咱們要使用GNU的gcc編譯器。 下面咱們以一個實例來講明如何使用gcc編譯器。

假設咱們有下面一個很是簡單的源程序(hello.c):工具

1 int main(int argc,char **argv)
2 {
3     printf("Hello Linux\n");
4 }

 

要編譯這個程序,咱們只要在命令行下執行:

學習

gcc -o hello hello.c

 

gcc 編譯器就會爲咱們生成一個hello的可執行文件。執行./hello就能夠看到程序的輸出結果了。命令行中 gcc表示咱們是用gcc來編譯咱們的源程序,-o 選項表示咱們要求編譯器給咱們輸出的可執行文件名爲hello 而hello.c是咱們的源程序文件。

gcc編譯器有許多選項,通常來講咱們只要知道其中的幾個就夠了。 -o選項咱們已經知道了,表示咱們要求輸出的可執行文件名。 -c選項表示咱們只要求編譯器輸出目標代碼,而沒必要要輸出可執行文件。 -g選項表示咱們要求編譯器在編譯的時候提供咱們之後對程序進行調試的信息。

知道了這三個選項,咱們就能夠編譯咱們本身所寫的簡單的源程序了,若是你想要知道更多的選項,能夠查看gcc的幫助文檔,那裏有着許多對其它選項的詳細說明。

2.Makefile的編寫

假設咱們有下面這樣的一個程序,源代碼以下:

spa

 1 /* main.c */
 2 #include "mytool1.h"
 3 #include "mytool2.h"
 4 int main(int argc,char **argv)
 5 {
 6     mytool1_print("hello");
 7     mytool2_print("hello");
 8 }
 9 
10 /* mytool1.h */
11 #ifndef _MYTOOL_1_H
12 #define _MYTOOL_1_H
13 void mytool1_print(char *print_str);
14 #endif
15 
16 /* mytool1.c */
17 #include "mytool1.h"
18 void mytool1_print(char *print_str)
19 {
20     printf("This is mytool1 print %s\n",print_str);
21 }
22 
23 /* mytool2.h */
24 #ifndef _MYTOOL_2_H
25 #define _MYTOOL_2_H
26 void mytool2_print(char *print_str);
27 #endif
28 
29 /* mytool2.c */
30 #include "mytool2.h"
31 void mytool2_print(char *print_str)
32 {
33     printf("This is mytool2 print %s\n",print_str);
34 }


固然因爲這個程序是很短的咱們能夠這樣來編譯

.net

1 gcc -c main.c
2 gcc -c mytool1.c
3 gcc -c mytool2.c
4 gcc -o main main.o mytool1.o mytool2.o

 

這樣的話咱們也能夠產生main程序,並且也不時很麻煩。可是若是咱們考慮一下若是有一天咱們修改了其中的一個文件(好比說mytool1.c)那麼咱們 難道還要從新輸入上面的命令?也許你會說,這個很容易解決啊,我寫一個SHELL腳本,讓她幫我去完成不就能夠了。是的對於這個程序來講,是能夠起到做用 的。可是當咱們把事情想的更復雜一點,若是咱們的程序有幾百個源程序的時候,難道也要編譯器從新一個一個的去編譯?

爲此,聰明的程序員們想出了一個很好的工具來作這件事情,這就是make。咱們只要執行如下make,就能夠把上面的問題解決掉。在咱們執行make之 前,咱們要先編寫一個很是重要的文件。--Makefile。對於上面的那個程序來講,可能的一個Makefile的文件是:

# 這是上面那個程序的Makefile文件

命令行

 1 main:main.o mytool1.o mytool2.o
 2 gcc -o main main.o mytool1.o mytool2.o
 3 
 4 main.o:main.c mytool1.h mytool2.h
 5 gcc -c main.c
 6 
 7 mytool1.o:mytool1.c mytool1.h
 8 gcc -c mytool1.c
 9 
10 mytool2.o:mytool2.c mytool2.h
11 gcc -c mytool2.c


有了這個Makefile文件,不過咱們何時修改了源程序當中的什麼文件,咱們只要執行make命令,咱們的編譯器都只會去編譯和咱們修改的文件有關的文件,其它的文件她連理都不想去理的。

下面咱們學習Makefile是如何編寫的。

在Makefile中也#開始的行都是註釋行.Makefile中最重要的是描述文件的依賴關係的說明。通常的格式是:

線程

target:components
TAB rule

  

第一行表示的是依賴關係。第二行是規則。

好比說咱們上面的那個Makefile文件的第二行

調試

main:main.o mytool1.o mytool2.o

  

表示咱們的目標(target)main的依賴對象(components)是main.o mytool1.o mytool2.o 當倚賴的對象在目標修改後修改的話,就要去執行規則一行所指定的命令。就象咱們的上面那個Makefile第三行所說的同樣要執行 gcc -o main main.o mytool1.o mytool2.o 注意規則一行中的TAB表示那裏是一個TAB鍵

Makefile有三個很是有用的變量。分別是$@,$^,$<表明的意義分別是:

$@--目標文件,$^--全部的依賴文件,$<--第一個依賴文件。

若是咱們使用上面三個變量,那麼咱們能夠簡化咱們的Makefile文件爲:

# 這是簡化後的Makefile

 1 main:main.o mytool1.o mytool2.o
 2 gcc -o $@ $^
 3 
 4 main.o:main.c mytool1.h mytool2.h
 5 gcc -c $<
 6 
 7 mytool1.o:mytool1.c mytool1.h
 8 gcc -c $<
 9 
10 mytool2.o:mytool2.c mytool2.h
11 gcc -c $<

 

通過簡化後咱們的Makefile是簡單了一點,不過人們有時候還想簡單一點。這裏咱們學習一個Makefile的缺省規則

1 .c.o:
2 gcc -c $<


這個規則表示全部的 .o文件都是依賴與相應的.c文件的。例如mytool.o依賴於mytool.c這樣Makefile還能夠變爲:

# 這是再一次簡化後的Makefile

main:main.o mytool1.o mytool2.o
gcc -o $@ $^
.c.o:
gcc -c $<

好了,咱們的Makefile 也差很少了,若是想知道更多的關於Makefile規則能夠查看相應的文檔。


3.程序庫的連接

試着編譯下面這個程序

1 /* temp.c */
2 
3 #include
4 int main(int argc,char **argv)
5 {
6     double value;
7     printf("Value:%f\n",value);
8 }

這個程序至關簡單,可是當咱們用 gcc -o temp temp.c 編譯時會出現下面所示的錯誤。

/tmp/cc33Kydu.o: In function `main':
/tmp/cc33Kydu.o(.text+0xe): undefined reference to `log'
collect2: ld returned 1 exit status

  出現這個錯誤是由於編譯器找不到log的具體實現。雖然咱們包括了正確的頭文件,可是咱們在編譯的時候仍是要鏈接肯定的庫。在Linux下,爲了使用數學 函數,咱們必須和數學庫鏈接,爲此咱們要加入 -lm 選項。 gcc -o temp temp.c -lm這樣纔可以正確的編譯。也許有人要問,前面咱們用printf函數的時候怎麼沒有鏈接庫呢?是這樣的,對於一些經常使用的函數的實現,gcc編譯器會自 動去鏈接一些經常使用庫,這樣咱們就沒有必要本身去指定了。有時候咱們在編譯程序的時候還要指定庫的路徑,這個時候咱們要用到編譯器的 -L選項指定路徑。好比說咱們有一個庫在 /home/hoyt/mylib下,這樣咱們編譯的時候還要加上 -L/home/hoyt/mylib。對於一些標準庫來講,咱們沒有必要指出路徑。只要它們在起缺省庫的路徑下就能夠了。系統的缺省庫的路徑/lib /usr/lib /usr/local/lib 在這三個路徑下面的庫,咱們能夠不指定路徑。


還有一個問題,有時候咱們使用了某個函數,可是咱們不知道庫的名字,這個時候怎麼辦呢?很抱歉,對於這個問題我也不知道答案,我只有一個傻辦法。首先,我 到標準庫路徑下面去找看看有沒有和我用的函數相關的庫,我就這樣找到了線程(thread)函數的庫文件(libpthread.a)。 固然,若是找不到,只有一個笨方法。好比我要找sin這個函數所在的庫。 就只好用 nm -o /lib/*.so|grep sin>~/sin 命令,而後看~/sin文件,到那裏面去找了。在sin文件當中,我會找到這樣的一行libm-2.1.2.so:00009fa0 W sin 這樣我就知道了sin在 libm-2.1.2.so庫裏面,我用 -lm選項就能夠了(去掉前面的lib和後面的版本標誌,就剩下m了因此是 -lm)。 若是你知道怎麼找,請趕快告訴我,我回很是感激的。謝謝!

4.程序的調試

咱們編寫的程序不太可能一次性就會成功的,在咱們的程序當中,會出現許許多多咱們想不到的錯誤,這個時候咱們就要對咱們的程序進行調試了。

最經常使用的調試軟件是gdb.若是你想在圖形界面下調試程序,那麼你如今能夠選擇xxgdb.記得要在編譯的時候加入 -g選項.關於gdb的使用能夠看gdb的幫助文件。因爲我沒有用過這個軟件,因此我也不可以說出如何使用。不過我不喜歡用gdb.跟蹤一個程序是很煩的 事情,我通常用在程序當中輸出中間變量的值來調試程序的。固然你能夠選擇本身的辦法,沒有必要去學別人的。如今有了許多IDE環境,裏面已經本身帶了調試 器了。你能夠選擇幾個試一試找出本身喜歡的一個用。

5.頭文件和系統求助

有時候咱們只知道一個函數的大概形式,不記得確切的表達式,或者是不記得着函數在那個頭文件進行了說明。這個時候咱們能夠求助系統。

好比說咱們想知道fread這個函數的確切形式,咱們只要執行 man fread 系統就會輸出着函數的詳細解釋的。和這個函數所在的頭文件說明了。 若是咱們要write這個函數的說明,當咱們執行man write時,輸出的結果卻不是咱們所須要的。 由於咱們要的是write這個函數的說明,但是出來的倒是write這個命令的說明。爲了獲得write的函數說明咱們要用 man 2 write. 2表示咱們用的write這個函數是系統調用函數,還有一個咱們經常使用的是3表示函數是C的庫函數。

記住無論何時,man都是咱們的最好助手。

----------------------------------------

其餘參考資料

1.http://blog.csdn.net/u011964923/article/details/73297443

2.https://www.jianshu.com/p/5982ccb87af0

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息