亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

關于makefile

2019-11-17 05:15:06
字體:
來源:轉載
供稿:網友

  0) 介紹
本文將首先介紹為什么要將你的C源代碼分離成幾個合理的獨立檔案,什么時候需要分,
怎么才能分的好。然后將會告訴你GNU Make怎樣使你的編譯和連接步驟自動化。對于其它Make
工具的用戶來說,雖然在用其它類似工具時要做適當的調整,本文的內容仍然是非常有用的。
假如對你自己的編程工具有懷疑,可以實際地試一試,但請先閱讀用戶手冊。

1) 多文件項目
1.1 為什么使用它們?
首先,多文件項目的好處在那里呢?
它們看起來把事情弄得復雜無比。又要header文件,又要extern聲明,而且假如需要查找
一個文件,你要在更多的文件里搜索。但其實我們有很有力的理由支持我們把一個項目分解成
小塊。當你改動一行代碼,編譯器需要全部重新編譯來生成一個新的可執行文件。但假如你的
項目是分開在幾個小文件里,當你改動其中一個文件的時候,別的源文件的目標文件(object
files)已經存在,所以沒有什么原因去重新編譯它們。你所需要做的只是重現編譯被改動過的
那個文件,然后重新連接所有的目標文件罷了。在大型的項目中,這意味著從很長的(幾分鐘
到幾小時)重新編譯縮短為十幾,二十幾秒的簡單調整。只要通過基本的規劃,將一個項目分
解成多個小文件可使你更加輕易的找到一段代碼。很簡單,你根據代碼的作用把你的代碼分解
到不同的文件里。當你要看一段代碼時,你可以準確的知道在那個文件中去尋找它。從很多目
標文件生成一個程序包 (Library)比從一個單一的大目標文件生成要好的多。當然實際上這是
否真是一個優勢則是由你所用的系統來決定的。但是當使用 gcc/ld (一個 GNU C 編譯/連接
器) 把一個程序包連接到一個程序時,在連接的過程中,它會嘗試不去連接沒有使用到的部分。
但它每次只能從程序包中把一個完整的目標文件排除在外。因此假如你參考一個程序包中某一
個目標檔中任何一個符號的話,那么這個目標文件整個都會被連接進來。要是一個程序包被非
常充分的分解了的話,那么經連接后,得到的可執行文件會比從一個大目標文件組成的程序包
連接得到的文件小得多。又因為你的程序是很模塊化的,文件之間的共享部分被減到最少,那
就有很多好處——可以很輕易的追蹤到臭蟲,這些模塊經常是可以用在其它的項目里的,同時別
人也可以 更輕易的理解你的一段代碼是干什么的。當然此外還有許多別的好處……

1.2 何時分解你的項目
很明顯,把任何東西都分解是不合理的。象“世界,你們好”這樣的簡單程序根本就不能分,
因為實在也沒什么可分的。把用于測試用的小程序分解也是沒什么意思的。但一般來說,當分
解項目有助于布局、發展和易讀性的時候,我都會采取它。在大多數的情況下,這都是適用的。
(所謂“世界,你們好”,既 'hello world' ,只是一個介紹 一種編程語言時慣用的范例程序,
它會在屏幕上顯示一行 'hello world' 。是最簡單的程序。)假如你需要開發一個相當大的項
目,在開始前,應該考慮一下你將如何實現它,并且生 成幾個文件(用適當的名字)來放你的
代碼。當然,在你的項目開發的過程中,你可以 建立新的文件,但假如你這么做的話,說明你
可能改變了當初的想法,你應該想想是否 需要對整體結構也進行相應的調整。對于中型的項目,
你當然也可以采用上述技巧,但你也可以就那么開始輸入你的代碼, 當你的碼多到難以治理的
時候再把它們分解成不同的檔案。但以我的經驗來說,開始時 在腦子里形成一個大概的方案,
并且盡量遵從它,或在開發過程中,隨著程序的需要而 修改,會使開發變得更加輕易。

1.3 怎樣分解項目
先說明,這完全是我個人的意見,你可以(也許你真的會?)用別的方式來做。這會觸 動
到有關編碼風格的問題,而大家從來就沒有停止過在這個問題上的爭論。 在這里我只是給出我
自己喜歡的做法(同時也給出這么做的原因):
i) 不要用一個 header 文件指向多個源碼文件(例外:程序包的header文件)。 用一個
header定義一個源碼文件的方式 會更有效,也更輕易查尋。否則改變一個源文件的結構(并且
它的header文件)就必須重新編譯好幾個文件。
ii) 假如可以的話,完全可以用超過一個的 header 文件來指向同 一個源碼文件。有時將
不可公開調用的函數原型,類型定義 等等,從它們的C源碼文件中分離出來是非常有用的。使
用一個header文件裝公開符號,用另一個裝私人符號意味著假如 你改變了這個源碼文件的內部
結構,你可以只是重新編譯它而 不需要重新編譯那些使用它的公開 header 文件的其它的源文
件。
iii) 不要在多個 header 文件中重復定義信息。 假如需要, 在其中一個 header 文件里
#include 另一個,但是不要重復輸入相同的 header 信息兩次。原因是假如你以后改變了這個
信息,你只需要把它改變一次,不用搜索并改變另外一個重復的信息。
iv) 在每一個源碼文件里, #include 那些聲明了源碼文件中的符 號的所有 header 文件。
這樣一來,你在源碼文件和 header 文件對某些函數做出的矛盾聲明可以比較輕易的被編譯器發現。

1.4 對于常見錯誤的注釋
a) 定義符 (Identifier) 在源碼文件中的矛盾:在C里,變量和函數的缺 省狀態是公用的。
因此,任何C源碼檔案都可以引用存在于其它源 碼檔中的通用(global) 函數和通用變量,既使

這個檔案沒有那個變 量或函數的聲明或原型。因此你必須保證在不同的兩個檔案里不能 用同一
個符號名稱,否則會有連接錯誤或者在編譯時會有警告。一種避免這種錯誤的方法是在公用的符
號前加上跟其所在源文件有 關的前綴。比如:
所有在 gfx.c 里的函數都加上前綴“gfx_”。假如你很小心的分解你的程序,使用有 意義的
函數名稱,并且不是過分 使用通用變量,當然這根本就不是問題。 要防止一個符號在它被定義
的源文件以外被看到,可在它的定義前 加上要害字 “static”。這對只在一個檔案內部使用,其
它檔案都不會用到的簡單函數是很有用的。
b) 多次定義的符號: header 檔會被逐字的替換到你源文件里 #include 的位置的。 因此,
假如header檔被 #include 到一個以上的源文件 里,這個 header 檔中所有 的定義就會出現在
每一個有關的源碼文件 里。這會使它們里的符號被定義一次以上, 從而出現連接錯誤(見上)。
解決方法: 不要在 header 檔里定義變量。你只需要在 header 檔里聲明它們然后在 適當的C
源碼文件(應該 #include 那個 header 檔的那個)里定義它們(一次)。對于初學者來說,定
義和聲明是 很輕易混淆的。聲明的作用是告訴編譯器其所聲明的符 號應該存在,并且要有所指
定的類型。但是,它并不會使編譯器分配貯存空間。 而定 義的做用是要求編譯器分配貯存空間。
當做一個聲明而不是做 定義的時候,在聲明前放一個要害字“extern”。 例如,我們有一個叫
“counter”的變量,假如想讓它成為公用的,我們在一個源碼程 序(只在一個里面)的開始定義
它:“int counter;”,再在相關的 header 檔里聲明 它:“extern int counter;”。函數原型里
隱含著 extern 的意思,所以不需顧慮這個問題。
c) 重復定義,重復聲明,矛盾類型:
請考慮假如在一個C源碼文件中 #include 兩個檔 a.h 和 b.h, 而 a.h 又 #include 了 b.h
檔(原因是 b.h 檔定義了一些 a.h 需要的類型),會發生什么事呢?這時該C源碼文件
#include 了 b.h 兩次。因此每一個在 b.h 中的 #define 都發生了兩 次,每一 個聲明發生了
兩次,等等。理論上,因為它們是完全一樣的拷貝, 所以應該 不會有什么問題,但在實際應用
上,這是不符合C的語法 的,可能在編譯時出現錯誤,或至少是警告。 解決的方法是要確定每
一個 header 檔在任一個源碼文件中只被包 含了一次。我們一 般是用預處理器來達到這個目的
的。當我們進入 每一個 header 檔時,我們為這個 header 檔 #define一個巨集 指令。只有在
這個巨集指令沒有被定義的前提下,我們 才真正使用 該 header 檔的主體。在實際應用上,我
們只要簡單的把下面一段 碼放在 每一個 header 檔的開始部分:
#ifndef FILENAME_H
#define FILENAME_H
然后把下面一行碼放在最后:
#endif
用header檔的檔名(大寫的)代替上面的 FILENAME_H,用底線 代替檔名中的點。有些人喜歡
在 #endif 加上注釋來提醒他們這個 #endif 指的是什么。例如:
#endif /* #ifndef FILENAME_H */
我個人沒有這個習慣,因為這其實是很明顯的。當然這只是各人的 風格不同,無傷大雅。
你只需要在那些有編譯錯誤的 header 檔中加入這個技巧,但在所有的header檔中都加入也沒
什么損失,到底這是個好習慣。

1.5 重新編譯一個多文件項目
清楚的區別編譯和連接是很重要的。編譯器使用源碼文件來產生某種 形式的目標文件
(object files)。在這個過程中,外部的符號參考并 沒有被解釋或替換。 然后我們使用連接器
來連接這些目標文件和一些 標準的程序包再加你指定的程序包,最后連接生 成一個可執行程序。
在這個階段,一個目標文件中對別的文件中的符號的參考被解釋,并報告不能被解釋的參考,一
般是以錯誤信息的形式報告出來?;镜牟襟E就應該是,把你的源碼文件一個一個的編譯成目標
文件的格 式,最后把所 有的目標文件加上需要的程序包連接成一個可執行文件。具體怎么做是
由你的編譯器 決定的。這里我只給出 gcc(GNU C 編譯 器)的有關命令,這些有可能對你的非
gcc 編譯器也適用。
gcc 是一個多目標的工具。它在需要的時候呼叫其它的元件(預處理程序,編譯器,組合程
序,連接器)。具體的哪些元件被呼叫取決于 輸入文件的類型和你傳遞給它的開關。 一般來說,
假如你只給它C源碼文件,它將預處理,編譯,組合所有 的文件,然后把 所得的目標文件連接
成一個可執行文件(一般生成的 文件被命名為 a.out )。你當然可以這么做,但這會破壞很多
我們 把一個項目分解成多個文件所得到的好處。 假如你給它一個 -c 開關,gcc 只把給它的文
件編譯成目標文件, 用源碼文件的文件 名命名但把其后綴由“.c”或“.cc”變成“.o”。 假如你給
它的是一列目標文件, gcc 會把它們連接成可執行文件, 缺省文件名是a.out 。你可以改變缺
省名,用開 -o 后跟你指定 的文件名。因此,當你改變了一個源碼文件后,你需要重新編譯它:
'gcc -c filename.c' 然后重新連接你的項目: 'gcc -o exec_filename *.o'。 假如你改變了
一個 header 檔, 你需要重新編譯所有 #include 過 這個檔的源碼文件,你可以用
'gcc -c file1.c file2.c file3.c' 然后象上邊一樣連接。 當然這么做是很繁瑣的,幸虧我們
有些工具使這個步驟變得簡單。 本文的第二部分就 是介紹其中的一件工具:GNU Make 工具。
(好家伙,現在才開始見真章。您學到點兒東西沒?)


2) GNU Make 工具
2.1 基本 makefile 結構
GNU Make 的主要工作是讀進一個文本文件, makefile 。這個文件里主要是有關哪些文件
(‘target’目的文件)是從哪些別的 文件(‘dependencies’依靠文件)中產 生的,用什么命令
來進行 這個產生過程。有了這些信息, make 會檢查磁碟上的文件,假如 目的文件的時間戳
(該文件生成或被改動時的時間)比至少它的一 個依靠文件舊的話, make 就執行相應的命令,
以便更新目的文件。 (目的文件不一定是最后的可執行檔,它可以是任何一個文件。)
makefile 一般被叫做“makefile”或“Makefile”。當然你可以 在 make 的命令行指 定別的文件
名。假如你不非凡指定,它會尋 找“makefile”或“Makefile”,因此使用這兩個名字是最簡單的。
一個 makefile 主要含有一系列的規則,如下:
例如,考慮以下的 makefile :
=== makefile 開始 ===
myPRog : foo.o bar.o
gcc foo.o bar.o -o myprog
foo.o : foo.c foo.h bar.h
gcc -c foo.c -o foo.o
bar.o : bar.c bar.h
gcc -c bar.c -o bar.o
=== makefile 結束 ===
這是一個非?;镜?makefile —— make 從最上面開始,把上 面第一個目的, ‘myprog’,
做為它的主要目標(一個它需要保 證其總是最新的最終目標)。給出的 規則說明只要文件
‘myprog’ 比文件‘foo.o’或‘bar.o’中的任何一個舊,下一行的命令將 會被執行。但是,在
檢查文件 foo.o 和 bar.o 的時間戳之前,它會往下查找那些把 foo.o 或 bar.o 做為目標
文件的規則。它找到的關于 foo.o 的規則,該文件的依靠文件是 foo.c, foo.h 和 bar.h 。
它從下面再找不到生成這些依靠文件的規則,它就開始檢 查磁碟 上這些依靠文件的時間戳。
假如這些文件中任何一個的時間戳比 foo.o 的新, 命令 'gcc -o foo.o foo.c' 將會執行,
從而更新 文件 foo.o。接下來對文件 bar.o 做類似的檢查,依靠文件在這里是文件bar.c
和 bar.h 。 現在, make 回到‘myprog’的規則。假如剛才兩個規則中的任 何一個被執行,
myprog 就需要重建(因為其中一個 .o 檔就會比 ‘myprog’新),因此連接命令將被 執行。
希望到此,你可以看出使用 make 工具來建立程序的好處——前 一章中所有繁瑣的檢查
步驟都由 make 替你做了:檢查時間戳。 你的源碼文件里一個簡單改變都會造成那個文件
被重新編譯(因 為 .o 文件依靠 .c 文件),進而可執行文件被重新連接(因為.o文件被
改變了)。其實真正的得益是在當你改變一個 header 檔的時候——你不 再需要記住那個源
碼文件依靠它,因為所有的 資料都在 makefile 里。 make 會很輕松的替你重新編譯所有
那 些因依靠這個 header 文件而改變了的源碼文件,如有需 要,再 進行重新連接。當然,
你要確定你在 makefile 中所寫的規則是正確無誤的,只 列出那些在源碼文件 中被
#include 的 header 檔……

2.2 編寫 make 規則 (Rules)
最明顯的(也是最簡單的)編寫規則的方法是一個一個的查看源碼文件,把它們的目標
文件做為目的,而C源碼文件和被它 #include 的 header 檔做為依靠文件。但是你也要把
其它被這些 header 檔 #include 的 header 檔也列為依靠文件,還有那些被包括的文件所
包括的文件……然后你會發現要對越來越多的文件進行治理,然后你的頭發開始脫落,你的脾
氣開始變壞,你的臉 色變成菜色,你走在路上開始跟電線桿子 碰撞,終于你搗毀你的電腦
顯示器,停止編程。到低有沒有些輕易點兒的方法呢?當然有!向編譯器要!在編譯每一個
源碼文件的時候,它實在應 該知道應該包括什么樣的 header 檔。使用gcc的時候,用 -M
開關,它會為每一個你給它的C文件輸出一個規則,把目標文件 做為目的, 而這個C文件
和所有應該被 #include 的 header 文 件將做為依靠文件。注重這個規則會加入所有header
文件,包 括被角括號(`<', `>')和雙引號(`"')所包圍的文件。其實我們可以 相當肯定系統
header 檔(比如 stdio.h, stdlib.h 等等)不會 被我們更改,假如你用 -MM 來代替 -M
傳遞給 gcc, 那些用角括 號包圍的 header 檔將不會被包括。(這會節省一些編譯時間)
由 gcc 輸出的規則不會含有命令部分;你可以自己寫入你的命令 或者什么也不寫,而讓
make 使用它的隱含的規則(參考下面的 2.4 節)。
2.3 Makefile 變量
上面提到 makefiles 里主要包含一些規則。它們包含的其它的東 西是變量定義。
makefile 里的變量就像一個環境變量(environment variable)。 事實上,環境變量在make
過程中被解釋成 make 的變量。這些 變量是大小寫敏感的,一般使用大寫字母。 它們可以
從幾乎任何 地方被引用,也可以被用來做很多事情,比如:
i) 貯存一個文件名列表。在上面的例子里,生成可執行文件的 規則包含一些目標文件
名做為依靠。在這個規則的命令行 里同樣的那些文件被輸送給 gcc 做為命令參數。假如在
這里使用一個變數來貯存所有的目標文件名,加入新的目標 文件會變的簡單而且較不易出錯。
ii) 貯存可執行文件名。假如你的項目被用在一個非 gcc 的系 統里,或者假如你想使

用一個不同的編譯器,你必須將所 有使用編譯器的地方改成用新的編譯器名。但是假如使用
一 個變量來代替編譯器名,那么你只需要改變一個地方,其 它所有地方的命令名就都改變了。
iii) 貯存編譯器旗標。假設你想給你所有的編譯命令傳遞一組 相同的選項(例 -Wall -O -g);
假如你把這組選項存 入一個變量,那么你可以把這個變量放在所有 呼叫編譯器 的地方。而
當你要改變選項的時候,你只需在一個地方改 變這個變量的內 容。要設定一個變量, 你只
要在一行的開始寫下這個變量的名字,后 面跟一個 = 號,后面 跟你要設定的這個變量的值。
以后你要引用 這個變量,寫一個 $ 符號,后面是圍在括 號里的變量名。比如在 下面,我們
把前面的 makefile 利用變量重寫一遍:
=== makefile 開始 ===
OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g
myprog : $(OBJS)
$(CC) $(OBJS) -o myprog
foo.o : foo.c foo.h bar.h
$(CC) $(CFLAGS) -c foo.c -o foo.o
bar.o : bar.c bar.h
$(CC) $(CFLAGS) -c bar.c -o bar.o
=== makefile 結束 ===
還有一些設定好的內部變量,它們根據每一個規則內容定義。三個 比較有用的變量是
$@, $< 和 $^ (這些變量不需要括號括?。?。 $@ 擴展成當前規則的目的文件名, $< 擴
展成依靠列表中的第 一個依靠文件,而 $^ 擴展成整個依靠的列表(除掉了里面所有重復
的文件名)。利用這些變量,我們可以把上面的 makefile 寫成:
=== makefile 開始 ===
OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g
myprog : $(OBJS)
$(CC) $^ -o $@
foo.o : foo.c foo.h bar.h
$(CC) $(CFLAGS) -c $< -o $@
bar.o : bar.c bar.h
$(CC) $(CFLAGS) -c $< -o $@
=== makefile 結束 ===
你可以用變量做許多其它的事情,非凡是當你把它們和函數混合使用的時候。假如需要
更進一步的了解,請參考 GNU Make 手冊。('man make', 'man makefile')

2.4 隱含規則 (Implicit Rules)
請注重,在上面的例子里,幾個產生 .o 文件的命令都是一樣的。 都是從 .c 文件和
相關文件里產生 .o 文件,這是一個標準的步 驟。其實 make 已經知道怎么做——它 有一些
叫做隱含規則的內 置的規則,這些規則告訴它當你沒有給出某些命令的時候, 應該怎么辦。
假如你把生成 foo.o 和 bar.o 的命令從它們的規則中刪除, make 將會查找它的隱含規則,
然后會找到一個適當的命令。它的命令會 使用一些變量,因此你可以按照你的 想法來設定
它:它使用變量 CC 做為編譯器(象我們在前面的例子),并且傳遞變量 CFLAGS (給 C
編譯器,C++ 編譯器用 CXXFLAGS ),CPPFLAGS ( C 預 處理器旗 標), TARGET_ARCH
(現在不用考慮這個),然后它加 入旗標 '-c' ,后面跟變量 $< (第一個依靠名),然
后是旗 標 '-o' 跟變量 $@ (目的文件名)。
一個C編譯的 具體命令將 會是:$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
當然你可以按照你自己的需要來定義這些變量。這就是為什么用 gcc 的 -M 或 -MM 開
關輸出的碼可以直接用在一個 makefile 里。 2.5 假象目的 (Phony Targets) 假設你的一
個項目最后需要產生兩個可執行文件。你的主要目標 是產生兩個可執行文 件,但這兩個文
件是相互獨立的——假如一個文件需要重建,并不影響另一個。你可以使用“假象目的”來達到
這種效果。一個假象目的跟一個正常的目的幾乎是一樣 的, 只是這個目的文件是不存在的。
因此, make 總是會假設它需要 被生成,當把它 的依靠文件更新后,就會執行它的規則里
的命令 行。 假如在我們的 makefile 開始處輸入:
all : exec1 exec2
其中 exec1 和 exec2 是我們做為目的的兩個可執行文件。 make 把這個 'all' 做為
它的主要目的,每次執行時都會嘗試把 'all' 更新。但既然這行規則里沒有哪個命令來作
用在一個叫 'all' 的 實際文件(事實上 all 并不會在磁碟上實際產生),所以這個規則
并不真的改變 'all' 的狀態??杉热贿@個文件并不存在,所以 make 會嘗試 更新 all 規
則,因此就檢查它的依靠 exec1, exec2 是否需要更新,假如需要,就把 它們更新,從而
達到我們的目的。假象目的也可以用來描述一組非預設的動作。例如,你想把所有由make
產生的文件刪 除,你可以在 makefile 里設立這樣一個規則:
veryclean :
rm *.o
rm myprog
前提是沒有其它的規則依靠這個 'veryclean' 目的,它將永遠 不會被執行。但是,
假如你明確的使用命令 'make veryclean' , make 會把這個目的做為它的主要目標,執行
那些 rm 命令。假如你的磁碟上存在一個叫 veryclean 文件,會發生什么事?這 時因為在

這個規則里 沒有任何依靠文件,所以這個目的文件一定是 最新的了(所有的依靠文件都已
經是最 新的了),所以既使用戶明 確命令make重新產生它,也不會有任何事情發生。解決
方法是標 明所有的假象目的(用 .PHONY),這就告訴 make 不用檢查它們 是否存在 于磁
碟上,也不用查找任何隱含規則,直接假設指定的目 的需要被更新。在 makefile 里加入
下面這行包含上面規則的規則:
..PHONY : veryclean
就可以了。注重,這是一個非凡的 make 規則,make 知道 .PHONY 是一個非凡目的,
當然你可以在它的依靠里加入你想用的任何假象 目的,而 make 知道它們都是假象目 的。

2.6 函數 (Functions)
makefile 里的函數跟它的變量很相似——使用的時候,你用一個$符號跟開括號,函數
名,空格后跟一列由逗號分隔的參數,最后 用關括號結束。例如,在 GNU Make 里 有一
個叫 'wildcard' 的函 數,它有一個參數,功能是展開成一列所有符合由其參數 描述的
文 件名,文件間以空格間隔。你可以像下面所示使用這個命令:
SOURCES = $(wildcard *.c)
這行會產生一個所有以 '.c' 結尾的文件的列表,然后存入變量 SOURCES 里。當然
你不需要一定要把結果存入一個變量。
另一個有用的函數是 patsubst ( patten substitude, 匹配替 換的縮寫)函數。
它 需要3個參數——第一個是一個需要匹配的 式樣,第二個表示用什么來替換它,第三
個是一個需要被處理的 由空格分隔的字列。例如,處理那個經過上面定義后的變量,
OBJS = $(patsubst %.c,%.o,$(SOURCES))
這行將處理所有在 SOURCES 字列中的字(一列文件名),假如它的 結尾是 '.c' ,
就 用 '.o' 把 '.c' 取代。注重這里的 % 符號將匹 配一個或多個字符,而它每次所匹
配的字串叫做一個‘柄’(stem) 。 在第二個參數里, % 被解讀成用第一參數所匹配的 那個柄。

2.7 一個比較有效的 makefile
利用我們現在所學的,我們可以建立一個相當有效的 makefile 。 這個 makefile
可 以完成大部分我們需要的依靠檢查,不用做太大 的改變就可直接用在大多數的項目里。
首先我們需要一個基本的 makefile 來建我們的程序。我們可以讓 它搜索當前目錄,
找到源碼文件,并且假設它們都是屬于我們的項 目的,放進一個叫 SOURCES 的變量。
這里假如也包含所有的 *.cc 文件,也許會更保險,因為源碼文件可能是 C++ 碼的。
SOURCES = $(wildcard *.c *.cc) 利用 patsubst ,我們可以由源碼文件名產生目標文
件名,我們需 要編譯出這些目標 文件。假如我們的源碼文件既有 .c 文件,也有 .cc 文件,
我們需要使用相嵌的 patsubst 函數呼叫:
OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))
最里面一層 patsubst 的呼叫會對 .cc 文件進行后綴替代,產生的結 果被外層的
patsubst 呼叫處理,進行對 .c 文件后綴的替代。
現在我們可以設立一個規則來建可執行文件:
myprog : $(OBJS)
gcc -o myprog $(OBJS)
進一步的規則不一定需要, gcc 已經知道怎么去生成目標文件 (object files) 。下面我們可以設定產生依靠信息的規則:
depends : $(SOURCES)
gcc -M $(SOURCES) > depends
在這里假如一個叫 'depends' 的文件不存在,或任何一個源碼文件 比一個已存在的
depends 文件新,那么一個 depends 文件會被生 成。depends 文件將會含有由 gcc產生
的關于源碼文件的規則(注重-M開關)?,F在我們要讓 make 把這些規則當做 makefile
檔 的一部分。這里使用的技巧很像 C 語言中的 #include 系統——我 們要 求 make 把這
個文件 include 到 makefile 里,如下:
include depends
GNU Make 看到這個,檢查 'depends' 目的是否更新了,假如沒有, 它用我們給它
的命令重新產生 depends 檔。然后它會把這組(新) 規則包含進來,繼續處理最終目標
'myprog' 。當看到有關 myprog 的規則,它會檢查所有的目標文件是否更新——利用 depends
文件 里的規則,當然這些規則現在已經是更新過的了。
這個系統其實效率很低,因為每當一個源碼文件被改動,所有的源碼 文件都要被預處
理以產生一個新的 'depends' 文件。而且它也不是 100% 的安全,這是因為當一個 header
檔被改動,依靠信息并不會 被更新。但就基本工作來說,它也算相當有用的 了。

2.8 一個更好的 makefile
這是一個我為我大多數項目設計的 makefile 。它應該可以不需要修 改的用在大部分
項目里。我主要把它用在 djgpp 上,那是一個 DOS 版的 gcc 編譯器。因此你可以看到執
行的命令名、 'alleg' 程序包、 和 RM -F 變量都反映了這一點。
=== makefile 開始 ===
######################################
#
# Generic makefile

#
# by George Foot
# email: george.foot@merton.ox.ac.uk
#
# Copyright (c) 1997 George Foot
# All rights reserved.
# 保留所有版權
#
# No warranty, no liability;
# you use this at your own risk.
# 沒保險,不負責
# 你要用這個,你自己擔風險
#
# You are free to modify and
# distribute this without giving
# credit to the original author.
# 你可以隨便更改和散發這個文件
# 而不需要給原作者什么榮譽。
# (你好意思?)
#
######################################
### Customising
# 用戶設定
#
# Adjust the following if necessary; EXECUTABLE is the target
# executable's filename, and LIBS is a list of libraries to link in
# (e.g. alleg, stdcx, iostr, etc). You can override these on make's
# command line of course, if you prefer to do it that way.
#
# 假如需要,調整下面的東西。 EXECUTABLE 是目標的可執行文件名, LIBS
# 是一個需要連接的程序包列表(例如 alleg, stdcx, iostr 等等)。當然你
# 可以在 make 的命令行覆蓋它們,你愿意就沒問題。
#
EXECUTABLE := mushroom.exe
LIBS := alleg
# Now alter any implicit rules' variables if you like, e.g.:
#
# 現在來改變任何你想改動的隱含規則中的變量,例如
CFLAGS := -g -Wall -O3 -m486
CXXFLAGS := $(CFLAGS)
# The next bit checks to see whether rm is in your djgpp bin
# Directory; if not it uses del instead, but this can cause (harmless)
# `File not found' error messages. If you are not using DOS at all,
# set the variable to something which will unquestioningly remove
# files.
#
# 下面先檢查你的 djgpp 命令目錄下有沒有 rm 命令,假如沒有,我們使用 del 命令來代替,但有可能給我們 'File not found' 這個錯誤信息,這沒 # 什么大礙。假如你不是用 DOS ,把它設定成一個刪文件而不廢話的命令。 (其實這一步在 UNIX 類的系統上是多余的,只是方便 DOS 用戶。 UNIX 用戶可以刪除這5行命令。)

ifneq ($(wildcard $(DJDIR)/bin/rm.exe),)
RM-F := rm -f
else
RM-F := del
endif
# You shouldn't need to change anything below this point.
#
# 從這里開始,你應該不需要改動任何東西。(我是不太相信,太NB了?。?br />SOURCE := $(wildcard *.c) $(wildcard *.cc)
OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS))
$(patsubst %.d,%.cc,$(MISSING_DEPS)))
CPPFLAGS += -MD
..PHONY : everything deps objs clean veryclean rebuild
everything : $(EXECUTABLE)
deps : $(DEPS)
objs : $(OBJS)
clean :
@$(RM-F) *.o
@$(RM-F) *.d
veryclean: clean
@$(RM-F) $(EXECUTABLE)
rebuild: veryclean everything
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
@$(RM-F) $(patsubst %.d,%.o,$@)
endif
-include $(DEPS)
$(EXECUTABLE) : $(OBJS)
gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS))
=== makefile 結束 ===


有幾個地方值得解釋一下的。首先,我在定義大部分變量的時候使 用的是 :=
而不是=符號。它的作用是立即把定義中參考到的函 數和變量都展開了。假如使用=
的話,函數和變量參考會留在那 兒,就是說改變一個變量的值會導致其它變量的值
也被改變。例如:
A = foo
B = $(A)
# 現在 B 是 $(A) ,而 $(A) 是 'foo' 。
A = bar
# 現在 B 仍然是 $(A) ,但它的值已隨著變成 'bar' 了。
B := $(A)
# 現在 B 的值是 'bar' 。
A = foo
# B 的值仍然是 'bar' 。
make 會忽略在 # 符號后面直到那一行結束的所有文字。
ifneg...else...endif 系統是 makefile 里讓某一部分碼有條件的 失效/有效的工
具。 ifeq 使用兩個參數,假如它們相同,它把直 到 else (或者 endif ,假如沒有
else 的話)的一段碼加進 makefile 里;假如不同,把 else 到 endif 間的一段碼加入
makefile (假如有 else )。 ifneq 的用法剛好相反。 'filter-out' 函數使用兩個用
空格分開的列表,它把第二列表中所 有的存在于第一列 表中的項目刪除。我用它來處理
DEPS 列表,把所 有已經存在的項目都刪除,而只保留缺少的那些。
我前面說過, CPPFLAGS 存有用于隱含規則中傳給預處理器的一些 旗標。而 -MD 開
關 類似 -M 開關,但是從源碼文件 .c 或 .cc 中 形成的文件名是使用后綴.d 的(這就
解釋了我形成 DEPS 變量的 步驟)。DEPS 里提到的文件后來用 '-include' 加進了 makefile
里,它隱藏了所有因文件不存在而產生的錯誤信息。 假如任何依靠文件不存在, makefile
會把相應的 .o 文件從磁碟 上刪除,從而使得 make 重建它。因為 CPPFLAGS 指定了-MD,
它的 .d 文件也被重新產生。 最后, 'addprefix' 函數把第二個參數列表的每一項前綴
上第一個參數值。這個makefile的那些目的是(這些目的可以傳給make的命令行來直接選用):
everything:(預設) 更新主要的可執行程序,并且為每一個 源碼文件生成或更新一個 '.d'
文件和一個 '.o' 文件。 deps: 只是為每一個源碼程序產生或更新一個 '.d' 文件。
objs: 為每一個源碼程序生成或更新 '.d' 文件和目標文件。 clean: 刪除所有中介/
依靠文件( *.d 和 *.o )。
veryclean: 做 `clean' 和刪除可執行文件。
rebuild: 先做 `veryclean' 然后 `everything' ;既完全重建。
除了預設的 everything 以外,這里頭只有 clean , veryclean , 和rebuild 對用戶是
有意義的。 我還沒有發現當給出一個源碼文件的目錄,這個 makefile 會失敗的情況,除非依
靠文件被弄亂。假如這種弄亂的情況發生了,只要輸入 `make clean' , 所有的目標文件和依
靠文件會被刪除,問題就應該 被解決了。當然,最好不要把它們弄亂。假如你發現在某種情況
下這個makefile 文件不能完成它的工作,請告訴我,我會把它整好的。
3 總結
我希望這篇文章足夠具體的解釋了多文件項目是怎么運作的,也說明了 怎樣安全而合理的
使用它。到此,你應該可以輕松的利用 GNU Make 工 具來治理小型的項目,假如你完全理解了
后面幾個部分的話,這些對于 你來說應該沒什么困難。 GNU Make 是一件強大的工具,雖然它
主要是用來建立程序,它還有很多 別的用處。假如想要知道更多有關這個工具的知識,它的句
法,函數,和許多別的特點,你應該參看它的參考文件(info pages, 別的GNU工具也一樣, 看
它們的 info pages. )。


文章摘要:
  無論是在linux還是在Unix環境中,make都是一個非常重要的編譯命令。不管是自己進行項
目開發還是安裝應用軟件,我們都經常要用到make或make install,有效的利用make和makefile
工具可以大大提高項目開發的效率。但令人遺憾的是,在許多講述linux應用的書籍上都沒有具體
介紹這個功能強大但又非常復雜的編譯工具。在這里我就向大家具體介紹一下make及其描述文件makefile。
<http://member.netease.com/%7Ehalon/unixfaq/unix_31.htm>

正文:  
linux/Unix環境下的make和makefile詳解  
Pathetique

  無論是在linux還是在Unix環境中,make都是一個非常重要的編譯命令。不管是自己進行項
目開發還是安裝應用軟件, 我們都經常要用到make或make install。利用make工具,我們可以
將大型的開發項目分解成為多個更易于治理的模塊, 對于一個包括幾百個源文件的應用程序,
使用make和makefile工具就可以簡潔明快地理順各個源文件之間紛繁復雜的相互關系。 而且如
此多的源文件,假如每次都要鍵入gcc命令進行編譯的話,那對程序員來說簡直就是一場災難。
而make工具則可自動完成編譯工作,并且可以只對程序員在上次編譯后修改過的部分進行編譯。

因此,有效的利用make和makefile工具可以大大提高項目開發的效率。同時把握make和makefile
之后,您也不會再面對著linux下的應用軟件手足無措了。
  但令人遺憾的是,在許多講述linux應用的書籍上都沒有具體介紹這個功能強大但又非常復
雜的編譯工具。在這里我就向大家具體介紹一下make及其描述文件makefile。

Makefile文件

  Make工具最主要也是最基本的功能就是通過makefile文件來描述源程序之間的相互關系并自
動維護編譯工作。而makefile文件需要按照某種語法進行編寫,文件中需要說明如何編譯各個源
文件并連接生成可執行文件,并要求定義源文件之間的依靠關系。makefile文件是許多編譯器--
包括 Windows NT 下的編譯器--維護編譯信息的常用方法,只是在集成開發環境中,用戶通過友
好的界面修改 makefile 文件而已。
  在UNIX系統中,習慣使用Makefile作為makfile文件。假如要使用其他文件作為 makefile,
則可利用類似下面的 make 命令選項指定 makefile 文件:
  $ make -f Makefile.debug
  例如,一個名為prog的程序由三個C源文件filea.c、fileb.c和filec.c以及庫文件LS編譯生
成,這三個文件還分別包含自己的頭文件a.h 、b.h和c.h。通常情況下, C編譯器將會輸出三個
目標文件filea.o、fileb.o和filec.o。 假設filea.c和fileb.c都要聲明用到一個名為defs的文
件,但filec.c不用。即在filea.c和fileb.c里都有這樣的聲明:
  #include "defs"
  那么下面的文檔就描述了這些文件之間的相互聯系:
  ---------------------------------------------------------
   #It is a example for describing makefile
   prog : filea.o fileb.o filec.o
   cc filea.o fileb.o filec.o -LS -o prog
   filea.o : filea.c a.h defs
   cc -c filea.c
   fileb.o : fileb.c b.h defs
   cc -c fileb.c
   filec.o : filec.c c.h
   cc -c filec.c
  ----------------------------------------------------------
  這個描述文檔就是一個簡單的makefile文件。
  從上面的例子注重到,第一個字符為 # 的行為注釋行。第一個非注釋行指定prog由三個目
標文件filea.o、fileb.o和filec.o鏈接生成。第三行描述了如何從prog所依靠的文件建立可執
行文件。接下來的4、6、8行分別指定三個目標文件,以及它們所依靠的.c和.h文件以及defs文
件。而5、7、9行則指定了如何從目標所依靠的文件建立目標。
  當filea.c或a.h文件在編譯之后又被修改,則 make 工具可自動重新編譯filea.o,假如在
前后兩次編譯之間,filea.C 和a.h 均沒有被修改,而且 test.o 還存在的話, 就沒有必要重
新編譯。這種依靠關系在多源文件的程序編譯中尤其重要。通過這種依靠關系的定義,make 工
具可避免許多不必要的編譯工作。當然,利用 Shell 腳本也可以達到自動編譯的效果,但是,
Shell 腳本將全部編譯任何源文件,包括哪些不必要重新編譯的源文件,而 make 工具則可根據
目標上一次編譯的時間和目標所依靠的源文件的更新時間而自動判定應當編譯哪個源文件。
Makefile文件作為一種描述文檔一般需要包含以下內容:
  ◆ 宏定義
  ◆ 源文件之間的相互依靠關系
  ◆ 可執行的命令
  Makefile中答應使用簡單的宏指代源文件及其相關編譯信息,在linux中也稱宏為變量。在
引用宏時只需在變量前加$符號,但值得注重的是,假如變量名的長度超過一個字符,在引用時
就必須加圓括號()。
  下面都是有效的宏引用:
  $(CFLAGS)
  $2
  $Z
  $(Z)
  其中最后兩個引用是完全一致的。
  需要注重的是一些宏的預定義變量,在Unix系統中,$*、$@、$?和$<四個非凡宏的值在執行
命令的過程中會發生相應的變化,而在GNU make中則定義了更多的預定義變量。關于預定義變量
的具體內容,
  宏定義的使用可以使我們脫離那些冗長乏味的編譯選項,為編寫makefile文件帶來很大的方便。
  ---------------------------------------------------------
   # Define a macro for the object files
   OBJECTS= filea.o fileb.o filec.o

   # Define a macro for the library file
   LIBES= -LS

   # use macros rewrite makefile
   prog: $(OBJECTS)
   cc $(OBJECTS) $(LIBES) -o prog
   ……
  ---------------------------------------------------------
  此時假如執行不帶參數的make命令,將連接三個目標文件和庫文件LS;但是假如在make命令后帶有新的宏定義:
  make "LIBES= -LL -LS"
則命令行后面的宏定義將覆蓋makefile文件中的宏定義。若LL也是庫文件,此時make命令將

連接三個目標文件以及兩個庫文件LS和LL。
  在Unix系統中沒有對常量NULL作出明確的定義,因此我們要定義NULL字符串時要使用下述宏定義:
  STRINGNAME=

Make命令

  在make命令后不僅可以出現宏定義,還可以跟其他命令行參數,這些參數指定了需要編譯
的目標文件。其標準形式為:
  target1 [target2 …]:[:][dependent1 …][;commands][#…]
  [(tab) commands][#…]
  方括號中間的部分表示可選項。Targets和dependents當中可以包含字符、數字、句點和"/"
符號。除了引用,commands中不能含有"#",也不答應換行。
  在通常的情況下命令行參數中只含有一個":",此時command序列通常和makefile文件中某些
定義文件間依靠關系的描述行有關。假如與目標相關連的那些描述行指定了相關的command序列,
那么就執行這些相關的command命令,即使在分號和(tab)后面的aommand字段甚至有可能是NULL。
假如那些與目標相關連的行沒有指定command,那么將調用系統默認的目標文件生成規則。
  假如命令行參數中含有兩個冒號"::",則此時的command序列也許會和makefile中所有描述
文件依靠關系的行有關。此時將執行那些與目標相關連的描述行所指向的相關命令。同時還將執
行build-in規則。
  假如在執行command命令時返回了一個非"0"的出錯信號,例如makefile文件中出現了錯誤的
目標文件名或者出現了以連字符打頭的命令字符串,make操作一般會就此終止,但假如make后帶
有"-i"參數,則make將忽略此類出錯信號。
  Make命本身可帶有四種參數:標志、宏定義、描述文件名和目標文件名。其標準形式為:
  Make [flags] [macro definitions] [targets]
  Unix系統下標志位flags選項及其含義為:
  -f file  指定file文件為描述文件,假如file參數為"-"符,那么描述文件指向標準輸入。
假如沒有"-f"參數,則系統將默認當前目錄下名為makefile或者名為Makefile的文件為描述文件。
在linux中, GNU make 工具在當前工作目錄中按照GNUmakefile、makefile、Makefile的順序搜索
makefile文件。
  -i   忽略命令執行返回的出錯信息。
  -s   沉默模式,在執行之前不輸出相應的命令行信息。
  -r   禁止使用build-in規則。
  -n   非執行模式,輸出所有執行命令,但并不執行。
  -t   更新目標文件。
  -q   make操作將根據目標文件是否已經更新返回"0"或非"0"的狀態信息。
  -p   輸出所有宏定義和目標文件描述。
  -d   Debug模式,輸出有關文件和檢測時間的具體信息。
  linux下make標志位的常用選項與Unix系統中稍有不同,下面我們只列出了不同部分:
  -c dir   在讀取 makefile 之前改變到指定的目錄dir。
  -I dir   當包含其他 makefile文件時,利用該選項指定搜索目錄。
  -h   help文擋,顯示所有的make選項。
  -w   在處理 makefile 之前和之后,都顯示工作目錄。
  通過命令行參數中的target,可指定make要編譯的目標,并且答應同時定義編譯多個目標,
操作時按照從左向右的順序依次編譯target選項中指定的目標文件。假如命令行中沒有指定目標,
則系統默認target指向描述文件中第一個目標文件。
  通常,makefile 中還定義有 clean 目標,可用來清除編譯過程中的中間文件,例如:
  clean:
  rm -f *.o
  運行 make clean 時,將執行 rm -f *.o 命令,最終刪除所有編譯過程中產生的所有中間文件。

隱含規則
  在make 工具中包含有一些內置的或隱含的規則, 這些規則定義了如何從不同的依靠文件建
立特定類型的目標。Unix系統通常支持一種基于文件擴展名即文件名后綴的隱含規則。這種后綴
規則定義了如何將一個具有特定文件名后綴的文件(例如.c文件),轉換成為具有另一種文件名
后綴的文件(例如.o文件):
  .c:.o
  $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
  系統中默認的常用文件擴展名及其含義為:
  .o  目標文件
  .c  C源文件
  .f  FORTRAN源文件
  .s  匯編源文件
  .y  Yacc-C源語法
  .l  Lex源語法
  在早期的Unix系統系統中還支持Yacc-C源語法和Lex源語法。在編譯過程中, 系統會首先在
makefile文件中尋找與目標文件相關的.C文件,假如還有與之相依靠的.y和.l文件,則首先將其
轉換為.c文件后再編譯生成相應的.o文件;假如沒有與目標相關的.c文件而只有相關的.y文件,
則系統將直接編譯.y文件。
  而GNU make 除了支持后綴規則外還支持另一種類型的隱含規則--模式規則。 這種規則更加
通用,因為可以利用模式規則定義更加復雜的依靠性規則。模式規則看起來非常類似于正則規則,
但在目標名稱的前面多了一個 %號,同時可用來定義目標和依靠文件之間的關系,例如下面的模
式規則定義了如何將任意一個 file.c 文件轉換為 file.o 文件:
  %.c:%.o
  $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<

#EXAMPLE#

  下面將給出一個較為全面的示例來對makefile文件和make命令的執行進行進一步的說明,其

中make命令不僅涉及到了C源文件還包括了Yacc語法。本例選自"Unix Programmer's Manual 7th
Edition, Volume 2A" Page 283-284
  下面是描述文件的具體內容:
  ---------------------------------------------------------
   #Description file for the Make command
   #Send to print
   P=und -3 opr -r2
   #The source files that are needed by object files
   FILES= Makefile version.c defs main.c donamc.c misc.c file.c
   dosys.c gram.y lex.c gcos.c
   #The definitions of object files
   OBJECTS= vesion.o main.o donamc.o misc.o file.o dosys.o gram.o
   LIBES= -LS
   LINT= lnit -p
   CFLAGS= -O
   make: $(OBJECTS)
   cc $(CFLAGS) $(OBJECTS) $(LIBES) -o make
   size make
   $(OBJECTS): defs
   gram.o: lex.c
   cleanup:
   -rm *.o gram.c
   install:
   @size make /usr/bin/make
   cp make /usr/bin/make ; rm make
   #print recently changed files
   print: $(FILES)
   pr $? $P
   toUCh print
   test:
   make -dp grep -v TIME>1zap
   /usr/bin/make -dp grep -v TIME>2zap
   diff 1zap 2zap
   rm 1zap 2zap
   lint: dosys.c donamc.c file.c main.c misc.c version.c gram.c
   $(LINT) dosys.c donamc.c file.c main.c misc.c version.c
   gram.c
   rm gram.c
   arch:
   ar uv /sys/source/s2/make.a $(FILES)
  ----------------------------------------------------------
  通常在描述文件中應象上面一樣定義要求輸出將要執行的命令。在執行了make命令之后,輸
出結果為:
  $ make
  cc -c version.c
  cc -c main.c
  cc -c donamc.c
  cc -c misc.c
  cc -c file.c
  cc -c dosys.c
  yacc gram.y
  mv y.tab.c gram.c
  cc -c gram.c
  cc version.o main.o donamc.o misc.o file.o dosys.o gram.o
  -LS -o make
  13188+3348+3044=19580b=046174b

  最后的數字信息是執行"@size make"命令的輸出結果。之所以只有輸出結果而沒有相應的命
令行,是因為"@size make"命令以"@"起始,這個符號禁止打印輸出它所在的命令行。
  描述文件中的最后幾條命令行在維護編譯信息方面非常有用。 其中"print"命令行的作用是
打印輸出在執行過上次"make print"命令后所有改動過的文件名稱。 系統使用一個名為print的
0字節文件來確定執行print命令的具體時間, 而宏$?則指向那些在print文件改動過之后進行修
改的文件的文件名。假如想要指定執行print命令后,將輸出結果送入某個指定的文件, 那么就
可修改P的宏定義:
  make print "P= cat>zap"
  在linux中大多數軟件提供的是源代碼,而不是現成的可執行文件, 這就要求用戶根據自己
系統的實際情況和自身的需要來配置、編譯源程序后,軟件才能使用。只有把握了make工具,才
能讓我們真正享受到到linux這個自由軟件世界的帶給我們無窮樂趣。

上一篇:GCC

下一篇:編程資源收集

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
精品久久久一区| 国产精品爽爽爽爽爽爽在线观看| 久久久久久久久久久91| 69精品小视频| 亚洲无亚洲人成网站77777| 日韩精品亚洲视频| 亚洲欧美日韩一区二区在线| 亚洲色图综合久久| 欧美日韩在线看| 国产一区二区三区丝袜| 国产日韩综合一区二区性色av| 久久久精品日本| 亚洲精选一区二区| 欧洲日本亚洲国产区| 免费av在线一区| 中文字幕日韩视频| 亚洲最大成人在线| 成人疯狂猛交xxx| 91精品国产色综合| 亚洲欧美日韩精品| 久久国产精品视频| 国产亚洲欧洲高清| 欧美激情精品久久久久久蜜臀| 国产精品日韩专区| 国产日韩欧美中文| 91福利视频网| 中文字幕精品—区二区| 国产午夜精品麻豆| 午夜精品久久久久久久99热浪潮| 中文字幕日韩视频| 91久久嫩草影院一区二区| 亚洲国产欧美一区| 欧美日韩亚洲网| 在线日韩日本国产亚洲| 国产精品视频最多的网站| 国产成人精品久久二区二区| 国产三级精品网站| 中文字幕日韩高清| 久久综合免费视频影院| 在线观看亚洲视频| 国产精品欧美激情在线播放| 国产精品毛片a∨一区二区三区|国| 欧美激情精品久久久久久黑人| 91精品国产一区| 国产精欧美一区二区三区| 久久久久久美女| 精品国偷自产在线视频99| 亚洲人成亚洲人成在线观看| 黄色精品在线看| 日韩精品高清视频| 95av在线视频| 国产一区二区在线免费视频| 国产欧美一区二区三区四区| 精品日韩视频在线观看| 精品magnet| 欧美最猛黑人xxxx黑人猛叫黄| 欧美日韩不卡合集视频| 亚洲最大激情中文字幕| 久久精品国产亚洲7777| 中文字幕亚洲无线码a| 久久久久久亚洲| 欧美性69xxxx肥| 欧美激情一区二区三区高清视频| 亚洲香蕉成视频在线观看| 亚洲在线第一页| 欧美风情在线观看| 日韩欧美精品网站| 国产精品美女av| 欧美精品福利视频| 欧美乱人伦中文字幕在线| 欧美高清视频在线| 欧美精品一本久久男人的天堂| 欧美激情精品久久久久久久变态| 亚洲国产古装精品网站| 亚洲日韩中文字幕在线播放| 亚洲综合精品一区二区| 丝袜一区二区三区| 欧美巨乳在线观看| 91大神福利视频在线| 中文字幕日韩综合av| 亚洲精品视频在线播放| 欧美乱大交xxxxx| 国产精品第三页| 国产精品久久久久久久美男| 亚洲视频国产视频| 久热精品在线视频| 亚洲高清在线观看| 欧美亚洲国产日韩2020| 日韩欧美国产一区二区| 亚洲欧美中文在线视频| 国产日韩在线精品av| 久久综合网hezyo| 91久久精品美女高潮| 国产日韩综合一区二区性色av| 日韩成人av在线播放| 久久精品中文字幕一区| 国内精品久久久久| 亚洲国产精品悠悠久久琪琪| 国产精品久久久久久av下载红粉| 精品中文字幕久久久久久| 国内精品久久久久伊人av| 亚洲无av在线中文字幕| 国产91久久婷婷一区二区| 成人黄色片网站| 国产成人免费av| 全球成人中文在线| 亚洲精品福利在线观看| 国模叶桐国产精品一区| 国产福利精品av综合导导航| 97精品久久久中文字幕免费| 成人黄色免费片| 精品国产精品自拍| 亚洲综合在线播放| 国产激情久久久| 日韩av网站导航| 欧美电影在线观看完整版| 久久精品国产一区二区电影| 国产女精品视频网站免费| 精品视频—区二区三区免费| 亚洲精品久久久久中文字幕欢迎你| 欧美日韩成人在线播放| 国产精品毛片a∨一区二区三区|国| 亚洲a级在线播放观看| 精品自在线视频| 日韩风俗一区 二区| 国产99久久精品一区二区永久免费| 自拍偷拍亚洲欧美| 日韩成人中文字幕| 欧美日韩亚洲精品一区二区三区| 欧美黑人巨大xxx极品| 国产一区二区丝袜高跟鞋图片| 国内偷自视频区视频综合| 欧美黑人一级爽快片淫片高清| 国产精品吊钟奶在线| 亚洲精品国产精品久久清纯直播| 久久久久亚洲精品| 亚洲xxxx在线| 亚洲第一男人天堂| 欧美另类第一页| 亚洲欧美日韩区| 亚洲精品美女久久| 国产色视频一区| 精品无人区太爽高潮在线播放| 欧美激情videoshd| 亚洲电影免费观看高清完整版| 亚洲欧美另类在线观看| 亚洲在线一区二区| 爽爽爽爽爽爽爽成人免费观看| 97碰碰碰免费色视频| 欧美限制级电影在线观看| 国产欧美精品日韩| 一区二区国产精品视频| 亚洲一区二区三区毛片| 日韩中文字幕免费视频| 欧美最猛黑人xxxx黑人猛叫黄| 日韩久久免费视频| 午夜欧美不卡精品aaaaa| 欧美精品videosex极品1| 亚洲精品国产综合区久久久久久久| 国产精品电影久久久久电影网| 久久精品中文字幕| 久久久中精品2020中文| 永久免费精品影视网站| 国产精品电影网站|