使用make命令編譯項(xiàng)目文件入門
目錄:
一、make命令的運(yùn)行過程
二、基本gcc編譯命令
三、簡單Makefile文件的編寫
四、實(shí)例
一、make命令的運(yùn)行過程
在shell的提示符號(hào)下,若輸入"make",則它會(huì)到目前的目錄下找尋Makefile這個(gè)文件.然后依照Makefile中所記錄的步驟一步一步的來執(zhí)行.在我們寫程序的時(shí)候,如果事先就把compiler程式所需要的步驟先寫在Makefile中的話,想要compiler程序的時(shí)候就只要打入make的指令.只要程序無誤的話,就可以獲得所需要的結(jié)果了!
在項(xiàng)目文件中,如果有成百上千個(gè)源程序,每次修改其中的一個(gè)都需要全部重新編譯是不可想象的事情.但通過編輯Makefile文件,利用make命令就可以只針對(duì)其中修改的源文件進(jìn)行編譯,而不需要全體編譯.這就是make命令在編譯項(xiàng)目文件時(shí)體現(xiàn)出來的優(yōu)勢.能做到這點(diǎn),主要是基于Makefile文件的編寫,和make命令對(duì)Makefile文件的調(diào)用.Makefile文件作為make命令的默認(rèn)參數(shù),使一個(gè)基于依賴關(guān)系編寫的結(jié)構(gòu)文件.
大家經(jīng)??吹绞褂胢ake all, make install, make clean等命令,而他們處理的目標(biāo)都是一個(gè)Makefile文件,那么all、install、clean參數(shù)是如何調(diào)用Makefile文件的運(yùn)行呢?在這里,如果向上面的命令如果能夠正確運(yùn)行的話,那么在Makefile文件里一定有這樣的幾行,他們的以all、install、clean開始
all: ×××××××
×××××××××××
install: ××××××
×××××××××××
clean: ×××××××××
×××××××××××
all,install,clean我們可以用其他的變量來代替,他們是編譯時(shí)的一個(gè)參數(shù),在Makefile文件中作為一個(gè)標(biāo)志存在,也就是我們所說的目標(biāo).make all命令,就告訴make我們將執(zhí)行all所指定的目標(biāo).為了便于理解Make程序的流程,我們給大家看一個(gè)與gcc毫無關(guān)系的Makefile文件:
#Makefile begin
all:
@echo you have typed command "make all"
clean:
@echo you have typed command "make clean"
install:
@ehco you have typed command "make $@"
#Makefile end
注意在這里,all:、clean:、install:行要頂格些,而所有的@echo前要加tab鍵來跳格縮進(jìn).下面是運(yùn)行結(jié)果
[root@xxx test]#make all
you have typed command "make all"
[root@xxx test]#make clean
you have typed command "make clean"
[root@xxx test]#make install
you have typed command "make install"
二、基本gcc編譯命令
1、源程序的編譯
在Linux下面,使用GNU的gcc編譯器編譯一個(gè)C語言的源程序.下面我們簡單介紹幾個(gè)常用的Gcc編譯命令和參數(shù),這里不是講解Gcc的使用,只是介紹簡單的基礎(chǔ)知識(shí)是我們能看懂一般的makefile文件.
我們先看一個(gè)使用gcc編譯器的實(shí)例.假設(shè)我們有下面一個(gè)非常簡單的源程序(hello.c):
int main(int argc,char **argv)
{
printf("Hello Linux\n");
}
要編譯這個(gè)程序,我們只要在命令行下執(zhí)行: gcc -o hello hello.c
gcc 編譯器就會(huì)為我們生成一個(gè)hello的可執(zhí)行文件.執(zhí)行./hello就可以看到程序的輸出結(jié)果了.命令行中 gcc表示我們是用gcc來編譯我們的源程序,-o 選項(xiàng)表示我們要求編譯器給我們輸出的可執(zhí)行文件名為hello 而hello.c是我們的源程序文件.
gcc的基本格式就是:
gcc [-option] objectname sourcename
其中-option是參數(shù),用來控制gcc的編譯方式,常見的參數(shù)有如下幾個(gè):
-o 表示我們要求輸出的可執(zhí)行文件名:-o binaryname
-c 表示我們只要求編譯器進(jìn)行編譯,輸出目標(biāo)代碼,而不進(jìn)行連接: -c objectivename.o
-g 表示我們要求編譯器在編譯的時(shí)候提供我們以后對(duì)程序進(jìn)行調(diào)試的信息: -g
-O2 表示我們希望編譯器在編譯的時(shí)候?qū)ξ覀兊某绦蜻M(jìn)行一定程度的優(yōu)化.2表示我們優(yōu)化的級(jí)別是2.范
圍是1-3.不過習(xí)慣上我們都使用2的優(yōu)化級(jí)別.
-Wall是警告選項(xiàng),表示我們希望gcc在編譯的時(shí)候,讓gcc輸出她認(rèn)為的一些程序中可能會(huì)出問題的一些警
告信息,比如指針沒有初始化就進(jìn)行賦值等等一些警告信息.
-l 與之緊緊相連的是表示連接時(shí)所要的鏈接庫,比如多線程,如果你使用了pthread_create函數(shù),那么 你就應(yīng)該在編譯語句的最后加上"-lpthread","-l"表示連接,"pthread"表示要連接的庫,注意他們 在這里要連在一起寫.如:gcc -o test test1.o test2.o -lpthread
-I 表示將系統(tǒng)缺省的頭文件路徑擴(kuò)展到當(dāng)前路徑,默認(rèn)的路徑保存在/etc/ld.conf文件中。
gcc的例子:
gcc -c test.c,表示只編譯test.c文件,成功時(shí)輸出目標(biāo)文件test.o
gcc -o test test.o,將test.o連接成可執(zhí)行的二進(jìn)制文件test
gcc -o test test.c,將test.c編譯并連接成可執(zhí)行的二進(jìn)制文件test
gcc -c test.c -o test.o ,與上一條命令完全相同
gcc test.c -o test,與上一條命令相同
gcc -c test1.c,只編譯test1.c,成功時(shí)輸出目標(biāo)文件test1.o
gcc -c test2.c,只編譯test2.c,成功時(shí)輸出目標(biāo)文件test2.o
gcc -o test test1.o test2.o,將test1.o和test2.o連接為可執(zhí)行的二進(jìn)制文件test
gcc -c test test1.c test2.c,將test1.o和test2.o編譯并連接為可執(zhí)行的二進(jìn)制文件test
2、程序庫的鏈接
試著編譯下面這個(gè)程序
/* temp.c */
#include
int main(int argc,char **argv)
{
double value =15;
printf("Value:%f\n",log(value));
}
這個(gè)程序相當(dāng)簡單,但是當(dāng)我們用 gcc -o temp temp.c 編譯時(shí)會(huì)出現(xiàn)下面所示的錯(cuò)誤.
/tmp/cc33Kydu.o: In function `main':
/tmp/cc33Kydu.o(.text+0xe): undefined reference to `log'
collect2: ld returned 1 exit status
出現(xiàn)這個(gè)錯(cuò)誤是因?yàn)榫幾g器找不到log的具體實(shí)現(xiàn).雖然我們包括了正確的頭文件,但是我們在編譯的時(shí)候還是要連接確定的庫.在Linux下,為了使用數(shù)學(xué)函數(shù),我們必須和數(shù)學(xué)庫連接,為此我們要加入 -lm 選項(xiàng). gcc -o temp temp.c -lm這樣才能夠正確的編譯.也許有人要問,前面我們用printf函數(shù)的時(shí)候怎么沒有連接庫呢?是這樣的,對(duì)于一些常用的函數(shù)的實(shí)現(xiàn),gcc編譯器會(huì)自動(dòng)去連接一些常用庫,這樣我們就沒有必要自己去指定了. 有時(shí)候我們在編譯程序的時(shí)候還要指定庫的路徑,這個(gè)時(shí)候我們要用到編譯器的 -L選項(xiàng)指定路徑.比如說我們有一個(gè)庫在 /home/hoyt/mylib下,這樣我們編譯的時(shí)候還要加上 -L/home/hoyt/mylib.對(duì)于一些標(biāo)準(zhǔn)庫來說,我們沒有必要指出路徑.只要它們在起缺省庫的路徑下就可以了.系統(tǒng)的缺省庫的路徑/lib、/usr/lib、/usr/local/lib(你可以查看你的/etc/ld.conf文件來看看你的系統(tǒng)指定了那幾個(gè)缺省的路徑) 在這三個(gè)路徑下面的庫,我們可以不指定路徑.
還有一個(gè)問題,有時(shí)候我們使用了某個(gè)函數(shù),但是我們不知道庫的名字,這個(gè)時(shí)候怎么辦呢?很抱歉,對(duì)于這個(gè)問題我也不知道答案,我只有一個(gè)傻辦法.首先,我到標(biāo)準(zhǔn)庫路徑下面去找看看有沒有和我用的函數(shù)相關(guān)的庫,我就這樣找到了線程(thread)函數(shù)的庫文件(libpthread.a). 當(dāng)然,如果找不到,只有一個(gè)笨方法.比如我要找sin這個(gè)函數(shù)所在的庫. 就只好用 nm -o /lib/*.so|grep sin>~/sin 命令,然后看~/sin文件,到那里面去找了. 在sin文件當(dāng)中,我會(huì)找到這樣的一行l(wèi)ibm-2.1.2.so:00009fa0 W sin 這樣我就知道了sin在libm-2.1.2.so庫里面,我用 -lm選項(xiàng)就可以了(去掉前面的lib和后面的版本標(biāo)志,就剩下m了所以是 -lm). 如果你知道怎么找,請(qǐng)趕快告訴我,我回非常感激的.謝謝!
3、程序的調(diào)試
我們編寫的程序不太可能一次性就會(huì)成功的,在我們的程序當(dāng)中,會(huì)出現(xiàn)許許多多我們想不到的錯(cuò)誤,這個(gè)時(shí)候我們就要對(duì)我們的程序進(jìn)行調(diào)試了.最常用的調(diào)試軟件是gdb.如果你想在圖形界面下調(diào)試程序,那么你現(xiàn)在可以選擇xxgdb.記得要在編譯的時(shí)候加入-g選項(xiàng).關(guān)于gdb的使用可以看gdb的幫助文件. 由于我很少使用這個(gè)軟件,所以我也不能夠詳細(xì)的說出如何使用. 不過我不喜歡用gdb.跟蹤一個(gè)程序是很煩的事情,我一般用在程序當(dāng)中輸出中間變量的值來調(diào)試程序的.當(dāng)然你可以選擇自己的辦法,沒有必要去學(xué)別人的.現(xiàn)在有了許多IDE環(huán)境,里面已經(jīng)自己帶了調(diào)試器了.你可以選擇幾個(gè)試一試找出自己喜歡的一個(gè)用.
4、頭文件和系統(tǒng)求助
有時(shí)候我們只知道一個(gè)函數(shù)的大概形式,不記得確切的表達(dá)式,或者是不記得著函數(shù)在那個(gè)頭文件進(jìn)行了說明.這個(gè)時(shí)候我們可以求助系統(tǒng).比如說我們想知道 fread這個(gè)函數(shù)的確切形式,我們只要執(zhí)行 man fread 系統(tǒng)就會(huì)輸出著函數(shù)的詳細(xì)解釋的.和這個(gè)函數(shù)所在的頭文件說明了. 如果我們要write這個(gè)函數(shù)的說明,當(dāng)我們執(zhí)行man write時(shí),輸出的結(jié)果卻不是我們所需要的. 因?yàn)槲覀円氖莣rite這個(gè)函數(shù)的說明,可是出來的卻是write這個(gè)命令的說明.為了得到write的函數(shù)說明我們要用 man 2 write. 2表示我們用的write這個(gè)函數(shù)是系統(tǒng)調(diào)用函數(shù),還有一個(gè)我們常用的是3表示函數(shù)是C的庫函數(shù).
三、簡單Makefile文件的編寫
1、Makefile文件的一般組成
(1)注釋:
在Makefile中,任何以"#"起始的文字都是注釋,make在解釋Makefile的時(shí)候會(huì)忽略它們.
(2)轉(zhuǎn)接下行標(biāo)志:
在Makefile中,若一行不足以容納該命令的時(shí)候.可在此行之后加一個(gè)反斜線(\)表示下一行為本行的延續(xù)
,兩行應(yīng)視為一行處理
(3)宏(macro)
宏的格式為: =
例如:
CFLAGS = -O -systype bsd43
其實(shí)make本身已有許多的default的macro,如果要查看這些macro的話,可以用make -p的命令.
宏主要是作為運(yùn)行make時(shí)的一些環(huán)境變量的設(shè)置,比如制定編譯器等。
CC 表示我們的編譯器名稱,缺省值為cc.
CFLAGS 表示我們想給編譯器的編譯選項(xiàng)
LDLIBS 表示我們的在編譯的時(shí)候編譯器的連接庫選項(xiàng).(我們的這個(gè)程序中還用不到這個(gè)選項(xiàng))
(4)規(guī)則(Rules)
格式如下:
:
....
:
....
注意:需要頂格寫,而需要在下一行tab之后寫,由于其是一個(gè)批處理形式的文件,所以不
可以隨便的換行寫,被迫換行的時(shí)候要用上面的轉(zhuǎn)接下行標(biāo)志進(jìn)行連接.
(5)符號(hào)標(biāo)志及缺省規(guī)則
$@ 代指目標(biāo)文件
$< 第一個(gè)依賴文件
$^ 所有的依賴文件
$? 為該規(guī)則的依賴
- 若在command的前面加一個(gè)"-",表示若此command發(fā)生錯(cuò)誤不予理會(huì),繼續(xù)執(zhí)行下去.
$(macro) 應(yīng)用這個(gè)變量,可以自動(dòng)的將定義的宏加以展開,并替換使用。
.c.o:
gcc -c $< 這個(gè)規(guī)則表示所有的 .o文件都是依賴與其相應(yīng)的.c文件的.例如mytool.o依賴于mytool.c
再一次簡化后的Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
.c.o:
gcc -c $< -I.;
使用宏后進(jìn)一步的簡化Makefile可以是
CC=gcc
CFLAGS=-g -Wall -O2 -I.
main:main.o mytool1.o mytool2.o
.c.o:
2、依賴
我們現(xiàn)在提出這樣一個(gè)問題:我如何用一個(gè)make命令將替代所有的make all, make install,make clean命令呢?當(dāng)然我們可以象剛才那樣寫一個(gè)Makefile文件:
all:
@echo you have typed command "make all"
clean:
@echo you have typed command "make clean"
install:
@ehco you have typed command "make $@"
doall:
@echo you have typed command "make $@l"
@echo you have typed command "make all"
@echo you have typed command "make clean"
@ehco you have typed command "make install"
[root@xxx test]#make doall
you have typed command "make doall"
you have typed command "make all"
you have typed command "make clean"
you have typed command "make install"
在這里,doall:目標(biāo)有4調(diào)語句,他們都是連在一起并都是由tab鍵開始的.當(dāng)然,這樣能夠完成任務(wù),但是太笨了,我們這樣來寫:
[root@xxx test]#cat Makefile
# #表示Makefile文件中的注釋,下面是Makefile文件的具體內(nèi)容
all:
@echo you have typed command "make all"
clean:
@echo you have typed command "make clean"
install:
@ehco you have typed command "make $@"
doall: all clean install
@echo you have typed command "make $@l"
相信大家已經(jīng)看清了doall:的運(yùn)行方式,它先運(yùn)行all目標(biāo),然后運(yùn)行clean目標(biāo),然后是install,最后是自己本身的目標(biāo),并且每個(gè)$@還是保持著各自的目標(biāo)名稱.效果大致是一樣的。在這里,我們稱all, clean, install為目標(biāo)doall所依賴的目標(biāo),簡稱為doall的依賴.也就是你要執(zhí)行doall,請(qǐng)先執(zhí)行他們(all, clean, install),最后在執(zhí)行我的代碼.
注意依賴一定是Makefile里面的目標(biāo),否則你非要運(yùn)行;一般寫在最前邊,而不是像這樣寫在最后邊。
3、Makefile的編寫
在Makefile中,一般采用引導(dǎo)的注釋行開始;下邊一般緊跟macro定義;接下來是標(biāo)簽(如上面的all、clean等);最后就是Makefile中最重要的是描述文件的依賴關(guān)系的說明.一般的格式是:
#describe
macro
label: label1,label2
label1:
:
label2:
:
......
我們來看一個(gè)例子:
/* main.c */
#include
#include
int main(int argc,char **argv)
{
mytool1_print("hello");
mytool2_print("hello");
}
/* mytool1.h */
#ifndef _MYTOOL_1_H
#define _MYTOOL_1_H
void mytool1_print(char *print_str);
#endif
/* mytool1.c */
#include
void mytool1_print(char *print_str)
{
printf("This is mytool1 print %s\n",print_str);
}
/* mytool2.h */
#ifndef _MYTOOL_2_H
#define _MYTOOL_2_H
void mytool2_print(char *print_str);
#endif
/* mytool2.c */
#include
void mytool2_print(char *print_str)
{
printf("This is mytool2 print %s\n",print_str);
}
因?yàn)槲覀冊诔绦蛑惺褂昧宋覀冏约旱?個(gè)頭文件,而在包含這2個(gè)頭文件的時(shí)候,我們使用的是<> 這樣編譯器在編譯的時(shí)候會(huì)去系統(tǒng)默認(rèn)的頭文件路徑找我們的2個(gè)頭文件,由于我們的2個(gè)頭文件不在系統(tǒng)能夠的缺省路徑下面,所以我們自己擴(kuò)展系統(tǒng)的缺省路徑,為此我們使用了-I.選項(xiàng),表示將系統(tǒng)缺省的頭文件路徑擴(kuò)展到當(dāng)前路徑.這樣的話我們也可以產(chǎn)生main程序,而且也不是很麻煩.但是考慮一下如果有一天我們修改了其中的一個(gè)文件(比如說mytool1.c)那么我們難道還要重新逐一編譯?也許你會(huì)說,這個(gè)很容易解決啊,我寫一個(gè)SHELL腳本,讓她幫我去完成不就可以了.但是當(dāng)我們把事情想的更復(fù)雜一點(diǎn),如果我們的程序有幾百個(gè)源程序的時(shí)候,難道也要編譯器重新一個(gè)一個(gè)的去編譯? 為此,聰明的程序員們想出了一個(gè)很好的工具來做這件事情,這就是make.我們只要執(zhí)行一下make,就可以把上面的問題解決掉.在我們執(zhí)行make之前,我們要先編寫一個(gè)非常重要的文件.--Makefile.對(duì)于上面的那個(gè)程序來說,可能的一個(gè)Makefile的文件是:
# 這是上面那個(gè)程序的Makefile文件
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c -I.
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c -I.
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c -I.
有了這個(gè)Makefile文件,不管我們什么時(shí)候修改了源程序當(dāng)中的什么文件,我們只要執(zhí)行make命令,我們的編譯器都只會(huì)去編譯和我們修改的文件有關(guān)的文件,其它的文件她連理都不想去理的.
四、實(shí)例
1、實(shí)例一
一個(gè)非常簡單的Makefile
假設(shè)我們有一個(gè)程式,共分為下面的部份:
menu.c 主要的程式碼部份
menu.h menu.c的include file
utils.c 提供menu.c呼叫的一些function calls
utils.h utils.c的include file
同時(shí)本程式亦叫用了ncurses的function calls.
而menu.c和utils.c皆放在/usr/src/menu下.
但menu.h和utils.h卻放在/usr/src/menu/include下.
而程式做完之后,執(zhí)行檔名為menu且要放在/usr/bin下面.
# This is the Makefile of menu
CC = gcc
CFLAGS = -DDEBUG -c
LIBS = -lncurses
INCLUDE = -I/usr/src/menu/include
all: clean install
install: menu
chmod 750 menu
cp menu /usr/bin
menu: menu.o
$(CC) -o $@ $? $(LIBS)
menu.o:
$(CC) $(CFLAGS) -o $@ menu.c $(INCLUDE)
utils.o:
$(CC) $(CFLAGS) -o $@ utils.c $(INCLUDE)
clean:
-rm *.o
-rm *~
在上述的Makefile中,要使用某個(gè)macro可用$(macro_name)如此的形式.make會(huì)自動(dòng)的加以展開.
$@為該rule的Target,而$?則為該rule的depend.
若在command的前面加一個(gè)"-",表示若此command發(fā)生錯(cuò)誤則不予理會(huì),繼續(xù)執(zhí)行下去.
上述的Makefile的關(guān)系可以表示如下:
all
/ \
clean install
\
menu
/ \
menu.o utils.o
若只想清除source以外的檔案,可以打make clean;若只想做出menu.o可以打make menu.o;若想一次全部做完,可以打make all或是make;要特別注意的是command之前一定要有一個(gè)TAB(即TAB鍵).
2、實(shí)例二
有了上面的說明,我們就可以開始寫一些簡單的Makefile文件了.比如我們有如下結(jié)構(gòu)的文件:
tmp/
+---- include/
| +---- f1.h
| +----f2.h
+----f1.c #include "include/f1.h"
+----f2.c #include"include/f2.h"
+---main.c #include"include/f1.h", #include"include/f2.h"
要將他們聯(lián)合起來編譯為目標(biāo)為testmf的文件,我們就可以按下面的方式寫Makefile:
#Makefile,Create testmf from f1.c f2.c main.c
main: main.o f1.o f2.o
gcc -o testmf main.o f1.o f2.o
f1.o: f1.c
gcc -c -o file1.o file1.c
f2.o: f2.c
gcc -c -o file2.o file2.c
main.o
gcc -c -o main.o main.c
clean:
rm -rf f1.o f2.o main.o testmf
執(zhí)行這個(gè)Makefile文件
[root@xxx test]make
gcc -c -o main.o main.c
gcc -c -o file1.o file1.c
gcc -c -o file2.o file2.c
gcc -o testmf main.o f1.o f2.o
[root@xxx test]ls
f1.c f1.o f2.c f2.o main.c main.o include/ testmf
如果你的程序沒有問題的話,就應(yīng)該可以執(zhí)行了./testmf
這是一個(gè)很簡單的例子,但復(fù)雜的例子都是構(gòu)建在簡單功能基礎(chǔ)上的.后面將繼續(xù)介紹詳細(xì)的makefile的編寫方法。
參考連接地址:
http://bbs.ee.ntu.edu.tw/boards/Programming/17/12.html
http://www.linuxeden.com/forum/blog/index.php?op=ViewArticle&blogId=102509&articleId=341
http://www.douzhe.com/bbs/viewtopic.php?t=376&highlight=make
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)
點(diǎn)擊舉報(bào)。