• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            chaosuper85

            C++博客 首頁 新隨筆 聯系 聚合 管理
              118 Posts :: 0 Stories :: 3 Comments :: 0 Trackbacks

            #

                 摘要: Beej網絡socket編程指南 -------------------------------------------------------------------------------- 介紹   Socket 編程讓你沮喪嗎?從man pages中很難得到有用的信息嗎?你想跟上時代去編Internet相關的程序,但是為你在調用 connect() ...  閱讀全文
            posted @ 2009-08-04 00:59 chaosuper 閱讀(197) | 評論 (0)編輯 收藏

            在C++中,內存分成5個區,他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區。

              棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變量的存儲區。里面的變量通常是局部變量、函數參數等。

              堆,就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那么在程序結束后,操作系統會自動回收。

              自由存儲區,就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。

              全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++里面沒有這個區分了,他們共同占用同一塊內存區。

              常量存儲區,這是一塊比較特殊的存儲區,他們里面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改,而且方法很多,在《const的思考》一文中,我給出了6種方法)

              明確區分堆與棧

              在bbs上,堆與棧的區分問題,似乎是一個永恒的話題,由此可見,初學者對此往往是混淆不清的,所以我決定拿他第一個開刀。

              首先,我們舉一個例子:

            void f() { int* p=new int[5]; }

              這條短短的一句話就包含了堆與棧,看到new,我們首先就應該想到,我們分配了一塊堆內存,那么指針p呢?他分配的是一塊棧內存,所以這句話的意思就是:在棧內存中存放了一個指向一塊堆內存的指針p。在程序會先確定在堆中分配內存的大小,然后調用operator new分配內存,然后返回這塊內存的首地址,放入棧中,他在VC6下的匯編代碼如下:

            00401028 push 14h
            0040102A call operator new (00401060)
            0040102F add esp,4
            00401032 mov dword ptr [ebp-8],eax
            00401035 mov eax,dword ptr [ebp-8]
            00401038 mov dword ptr [ebp-4],eax

              這里,我們為了簡單并沒有釋放內存,那么該怎么去釋放呢?是delete p么?澳,錯了,應該是delete []p,這是為了告訴編譯器:我刪除的是一個數組,VC6就會根據相應的Cookie信息去進行釋放內存的工作。

              好了,我們回到我們的主題:堆和棧究竟有什么區別?

              主要的區別由以下幾點:

              1、管理方式不同;

              2、空間大小不同;

              3、能否產生碎片不同;

              4、生長方向不同;

              5、分配方式不同;

              6、分配效率不同;

              管理方式:對于棧來講,是由編譯器自動管理,無需我們手工控制;對于堆來說,釋放工作由程序員控制,容易產生memory leak。

              空間大小:一般來講在32位系統下,堆內存可以達到4G的空間,從這個角度來看堆內存幾乎是沒有什么限制的。但是對于棧來講,一般都是有一定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M(好像是,記不清楚了)。當然,我們可以修改:

              打開工程,依次操作菜單如下:Project->Setting->Link,在Category 中選中Output,然后在Reserve中設定堆棧的最大值和commit。

              注意:reserve最小值為4Byte;commit是保留在虛擬內存的頁文件里面,它設置的較大會使棧開辟較大的值,可能增加內存的開銷和啟動時間。

              碎片問題:對于堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對于棧來講,則不會存在這個問題,因為棧是先進后出的隊列,他們是如此的一一對應,以至于永遠都不可能有一個內存塊從棧中間彈出,在他彈出之前,在他上面的后進的棧內容已經被彈出,詳細的可以參考數據結構,這里我們就不再一一討論了。

              生長方向:對于堆來講,生長方向是向上的,也就是向著內存地址增加的方向;對于棧來講,它的生長方向是向下的,是向著內存地址減小的方向增長。

              分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。

              分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。

              從這里我們可以看到,堆和棧相比,由于大量new/delete的使用,容易造成大量的內存碎片;由于沒有專門的系統支持,效率很低;由于可能引發用戶態和核心態的切換,內存的申請,代價變得更加昂貴。所以棧在程序中是應用最廣泛的,就算是函數的調用也利用棧去完成,函數調用過程中的參數,返回地址,EBP和局部變量都采用棧的方式存放。所以,我們推薦大家盡量用棧,而不是用堆。

              雖然棧有如此眾多的好處,但是由于和堆相比不是那么靈活,有時候分配大量的內存空間,還是用堆好一些。

              無論是堆還是棧,都要防止越界現象的發生(除非你是故意使其越界),因為越界的結果要么是程序崩潰,要么是摧毀程序的堆、棧結構,產生以想不到的結果,就算是在你的程序運行過程中,沒有發生上面的問題,你還是要小心,說不定什么時候就崩掉,那時候debug可是相當困難的:)
            posted @ 2009-08-03 17:55 chaosuper 閱讀(141) | 評論 (0)編輯 收藏

             

            cisco研發中心面試試題
            說點關于cisco的面世吧:就算是給大家參考參考,可能對以后有幫助
            過程:4輪面試。兩個印度人,一個愛爾蘭人,一個臺灣人。每人面半個小時。首先是自我
            介紹了,這個相信大家誰都有準備。最好是4分鐘之內,突出重點:C/C++Linux/UnixVoIP,工作經驗或是其它。
            然后就是問問題。首先我認為大家一定要準備好自己的英語,因為面試官會根據自己的心情說快說慢,特別是印度工程師,聽起來比較費勁。下面說幾點技術相干問題:
            1.
            如果你對C++virtual function不是很懂,那么你很難突破。無論你到哪里面試,虛函數是一定要吃透的,我不知道現在方不方便說出具體的問題,但是大家應該對虛函數100%的了解,否則我認為很難拿到ciscooffer。轉摘請標明:http://www.pghome.net/art.html 志遠工作室
            2.
            關于線程:semaphoreshare memorycirtical sectionpthreadprocessmutex
            dead lock這些概念
            你應該很懂,隨便他怎么問你都能很好的回答出來,否則也懸。
            3. C++
            的零碎:對const100%理解;一個class基本的幾個元素:default constructordestructorcopy constructor
            overload assignmentpass by #118aluepass by reference的區別;這些你要熟到非常
            細微的地步,比如哪里的參數是
            const &
            ,返回值類型是reference還是&#118alue,要不要const。這些都是C++精髓,大家要是
            不是徹底弄懂,很難過cisco面試官
            的眼睛,因為問題都是圍繞這個來的。大家看自己是否面試好,只要問問自己對上面這些懂多少就可以了。
            4.
            對于二叉樹,起碼大家也應該知道是怎么回事,比如給你前序,中序你能畫出二叉樹,
            遞歸非遞歸算法怎么寫,如果刪除葉
            結點怎么寫......當然cisco沒問這么多,只是問了這里面的一個問題,我認為如果大家要
            面試,二叉樹一定要吃透,比如演變的
            二叉搜索樹,堆排序,這些都要記住,否則臨場很難發揮出來。當然cisco沒有問這么多,
            具體哪個我就不說,可能也應人而異,
            但是這些諸位都應該知道,如果二叉樹是什么都不知道,那么我覺得沒戲。
            5.
            現場編程。這個題目一般都很簡單,就要看心理素質怎樣。cisco出的題目不知道現在方不方便說,不過大概就是圍繞遞歸和非遞歸作文章。大家數據結構里面都學過,關鍵在于能否在非常短的時間之內準確的表達出自己的意思。這個我認為關鍵在于心理
            素質。如果編程你沒過,那么我就不知道怎樣了,但是你過了,而且很輕松的過,那么對于
            拿到offer是很有好處的。
            6.
            關于VoIP,這個主要集中在H.323SIP上。首先如果你對這兩個咚咚一竅不通,那么我
            個人認為很難通過。起碼,什么是什么應該知道,區別應該知道,兩個協議的相關協議完成的功能應該知道(當然SIP是依靠其它協議),拓撲應該知道,關鍵元素應該知道比如H.323里面的epgkbilling至少應該聽過,SIP中的proxyuaredirectregiste
            r
            應該知道什么咚咚,這些知道代表你知道這兩個是什么,如果前門這些不是全部知道(當然知道一項我想也沒問題,還有就是我只針對H.323SIP,其它這里不說),接下來cisco感興趣的是比如H.323的對話建立過程,比如ARQ-->ACF什么的,或者SIPinvite等等
            ,如果你能完美的畫出這個交互過程,那么你的VoIP100%滿足了cisco的要求了。不過個人認為除非你是開發過VoIP相關協議,否則這么清楚還是少的。當然只是個人見解。
            7.
            關于Linux:這個也是被問到的問題:基本操作:ifconfigtopps -ef|grep **,這
            些你用過Linux就知道。總的來說Linux基本上是針對基本的操作。就看你有沒有用過了。TCP相關編程,clientserver端的socket編程
            ,這個反正cisco是有高手,他會根據你知道多少問多少,起碼也應該知道一點內容吧。
            8. Why cisco
            。這是個主觀問題,可能也是考官打發剩于面試時間用的。呵呵,還是很重要。
            以上就是我對cisco面試的總接,目的很簡單,給大家分享,可能以后或者其它面試有幫助
            ,而不只是回憶cisco面試成功與否,這個已經不取決
            于我們,總結一下:轉摘請注明:www.pghome.net
            a.
            英語個人認為只要表達對方能夠聽懂就可以,不必要多么流利,因為印度工程師的英語
            實在是不敢恭維。當然你的聽力必須出色,專業名詞必須了如指掌
            b. C++
            應該至少熟悉:virtual function的所有方面;overload assignmentcopy constr
            uctor
            destructor
            c. VoIP
            :鑒于cisco招人的工作性質,你越懂這個你的砝碼越重
            d. Linux
            :和VoIP一樣
            e.
            編程素養:對數據結構的認識和臨場編程能力。進程之間通信和線程編程,網絡編程。
            f.
            你給考官的映象
            大家不妨參照上面的給自己分析分析,如果你每項都很牛,表現很好,那么我覺得你拿offer沒問題。
            以上純屬個人總結,目的在于分享和給面試其他公司或者是明年面試的人一些參考和建議,沒有任何泄露cisco面試考題的意思。

            posted @ 2009-08-03 14:45 chaosuper 閱讀(202) | 評論 (0)編輯 收藏

            http://linux.sheup.com/
            posted @ 2009-08-03 14:36 chaosuper 閱讀(104) | 評論 (0)編輯 收藏

            GCC 5

            Section: GNU Tools (1)
            Updated: 2003/12/05
            Sponsor: GCC Casino Winning Content

            下面的`-m'選項用于HPPA族計算機:

            -mpa-risc-1-0
            生成PA 1.0處理器的目標碼.
            -mpa-risc-1-1
            生成PA 1.1處理器的目標碼.

             

            -mkernel
            生成適用于內核的目標碼.特別要避免add指令,它有一個參數是DP寄存器;addil 代替add指令.這樣可以避免HP-UX連接器的某個嚴重bug.

             

            -mshared-libs
            生成能夠連接HP-UX共享庫的目標碼.該選項還沒有實現全部功能,PA目標默認為關閉.使用這個選項會導致 編譯器生成錯誤的目標碼.

             

            -mno-shared-libs
            不生成連接HP-UX共享庫的目標碼.這是PA目標的默認選項.

             

            -mlong-calls
            生成的目標碼允許同一個源文件中的函數調用,調用點和被調函數的距離可以超過256K之遠.不需要打開這個開關選項, 除非連接器給出``branch out of range errors``這樣的錯誤.

             

            -mdisable-fpregs
            防止任何情況下使用浮點寄存器.編譯內核需要這個選項,內核切換浮點寄存器的執行環境速度非常緩慢.如果打開了這個 開關選項同時試圖浮點操作,編譯將失敗.

             

            -mdisable-indexing
            防止編譯器使用索引地址模式(indexing address mode).這樣在MACH上編譯MIG生成的代碼時,可以 避免一些非常晦澀的問題.

             

            -mtrailing-colon
            在標記定義(label definition)的末尾添加一個冒號(用于ELF匯編器).

             

            下面的`-m'選項用于Intel 80960族計算機:

            -mcpu-type
            默認機器類型為cpu-type ,使編譯器產生對應的指令,地址模式和內存對齊.默認的 cpu-typekb;其他選擇有ka, mc, ca, cf, sa,sb.

             

            -mnumerics
            -msoft-float
            -mnumerics開關選項指出處理器不支持浮點指令. -msoft-float開關選項指出不應該認為 機器支持浮點操作.

             

            -mleaf-procedures
            -mno-leaf-procedures
            企圖(或防止)改變葉過程(leaf procedure),使其可被bal指令以及call指令 調用.對于直接函數調用,如果bal指令能夠被匯編器或連接器替換,這可以產生更有效的代碼,但是其他情況下 產生較低效的代碼,例如通過函數指針調用函數,或使用了不支持這種優化的連接器.

             

            -mtail-call
            -mno-tail-call
            執行(或不執行)更多的嘗試(除過編譯器那些機器無關部分),優化進入分支的尾遞歸(tail-recursive)調用.你 可能不需要這個,因為檢測什么地方無效沒有全部完成.默認開關是-mno-tail-call.

             

            -mcomplex-addr
            -mno-complex-addr
            認為(或不認為)在當前的i960設備上,值得使用復合地址模式(complex addressing mode).復合地址模式 可能不值得用到K系列,但是一定值得用在C系列.目前除了CBCC處理器,其他處理器上 -mcomplex-addr是默認選項.

             

            -mcode-align
            -mno-code-align
            把目標碼對齊到8字節邊界上(或者不必),這樣讀取會快一些.目前只對C系列默認打開.

             

            -mic-compat
            -mic2.0-compat
            -mic3.0-compat
            兼容iC960 v2.0v3.0.

             

            -masm-compat
            -mintel-asm
            兼容iC960匯編器.

             

            -mstrict-align
            -mno-strict-align
            不允許(或允許)邊界不對齊的訪問.

             

            -mold-align
            使結構對齊(structure-alignment)兼容Intelgcc發行版本1.3 (基于gcc 1.37).目前 這個選項有點問題,因為#pragma align 1總是作同樣的設定,而且無法關掉.

             

            下面的`-m'選項用于DEC Alpha設備:

            -mno-soft-float
            -msoft-float
            使用(或不使用)硬件浮點指令進行浮點運算.打開-msoft-float,將使用 `libgcc1.c'中的函數執行浮點運算.除非它們被仿真浮點操作的例程替換,或者類似,它們被編譯為調用 仿真例程,這些例程將發出浮點操作.如果你為不帶浮點操作的Alpha編譯程序,你必須確保建立了這個庫,以便不調用 仿真例程.

            注意,不帶浮點操作的Alpha也要求擁有浮點寄存器.

             

            -mfp-reg
            -mno-fp-regs
            生成使用(或不使用)浮點寄存器群的目標代碼. -mno-fp-regs包含有-msoft-float 開關選項.如果不使用浮點寄存器,浮點操作數就象整數一樣通過整數寄存器傳送,浮點運算結果放到$0而不是$f0.這是非標準 調用,因此任何帶有浮點參數或返回值的函數,如果被-mno-fp-regs開關編譯過的目標碼調用,它也必須 用這個選項編譯.

            這個選項的典型用法是建立內核,內核不使用任何浮點寄存器,因此沒必要保存和恢復這些寄存器.

             

            下面附加的選項出現在System V第四版中,用于兼容這些系統中的其他編譯器:

            -G
            SVr4系統中, gcc出于兼容接受了`-G'選項(然后傳遞給連接器).可是我們建議使用 `-symbolic'`-shared'選項,而不在gcc命令行上出現連接選項.

             

            -Qy
            驗證編譯器用的工具的版本,輸出到.ident匯編指令.

             

            -Qn
            制止輸出端的.ident指令(默認選項).

             

            -YP,dirs
            對于`-l'指定的庫文件,只搜索dirs.你可以在dirs中用冒號隔開各個 目錄項.

             

            -Ym,dir
            dir目錄中尋找M4預處理器.匯編器使用這個選項.

             

            代碼生成選項(CODE GENERATION OPTION)

            下面的選項和平臺無關,用于控制目標碼生成的接口約定.

            大部分選項以`-f'開始.這些選項擁有確定和否定兩種格式; `-ffoo'的否定格式是 `-fno-foo'.后面的描述將只列舉其中的一個格式---非默認的格式.你可以通過添加或去掉 `no-'推測出另一個格式.

            -fnonnull-objects
            假設通過引用(reference)取得的對象不為null (C++).

            一般說來, GNU C++對通過引用取得的對象作保守假設.例如,編譯器一定會檢查下似代碼中的a不為 null:

            obj &a = g (); a.f (2);

            檢查類似的引用需要額外的代碼,然而對于很多程序是不必要的.如果你的程序不要求這種檢查,你可以用 `-fnonnull-objects'選項忽略它.

             

            -fpcc-struct-return
            函數返回structunion值時,采用和本地編譯器相同的參數約定.對于較小的結構, 這種約定的效率偏低,而且很多機器上不能重入;它的優點是允許GCC編譯的目標碼和PCC編譯的目標碼互相調用.

             

            -freg-struct-return
            一有可能就通過寄存器返回structunion函數值.對于較小的結構,它比 -fpcc-struct-return更有效率.

            如果既沒有指定-fpcc-struct-return ,也沒有指定-freg-struct-return, GNU CC默認使用目標機的標準約定.如果沒有標準約定, GNU CC默認采用-fpcc-struct-return.

             

            -fshort-enums
            enum類型只分配它聲明的值域范圍的字節數.就是說, enum類型等于大小足夠的 最小整數類型.

             

            -fshort-double
            使double類型的大小和float一樣.

             

            -fshared-data
            要求編譯結果的數據和非const變量是共享數據,而不是私有數據.這種差別僅在某些操作系統上面有意義, 那里的共享數據在同一個程序的若干進程間共享,而私有數據在每個進程內都有副件.

             

            -fno-common
            即使未初始化的全局變量也分配在目標文件的bss,而不是把它們當做普通塊(common block)建立.這樣的 結果是,如果在兩個不同的編譯結果中聲明了同一個變量(沒使用extern ),連接它們時會產生錯誤. 這個選項可能有用的唯一情況是,你希望確認程序能在其他系統上運行,而其他系統總是這么做.

             

            -fno-ident
            忽略`#ident'指令.

             

            -fno-gnu-linker
            不要把全局初始化部件(C++的構造子和解構子)輸出為GNU連接器使用的格式(GNU連接器是標準方法的系統 上).當你打算使用非GNU連接器的時候可以用這個選項,GNU連接器也需要collect2程序確保系統連接器 放入構造子(constructor)和解構子(destructor). (GNU CC的發布包中包含有collect2 程序.)對于必須使用collect2的系統,編譯器驅動程序gcc自動配置為這么做.

             

            -finhibit-size-directive
            不要輸出.size匯編指令,或其他類似指令,當某個函數一分為二,兩部分在內存中距離很遠時會引起問題. 當編譯`crtstuff.c'時需要這個選項;其他情況下都不應該使用.

             

            -fverbose-asm
            輸出匯編代碼時放些額外的注釋信息.這個選項僅用于確實需要閱讀匯編輸出的時候(可能調試編譯器自己的時候).

             

            -fvolatile
            使編譯器認為所有通過指針訪問的內存是易變內存(volatile).

             

            -fvolatile-global
            使編譯器認為所有的外部和全局變量是易變內存.

             

            -fpic
            如果支持這種目標機,編譯器就生成位置無關目標碼.適用于共享庫(shared library).

             

            -fPIC
            如果支持這種目標機,編譯器就輸出位置無關目標碼.適用于動態連接(dynamic linking),即使分支需要大范圍 轉移.

             

            -ffixed-reg
            把名為reg的寄存器按固定寄存器看待(fixed register);生成的目標碼不應該引用它(除了或許 用作棧指針,幀指針,或其他固定的角色).

            reg必須是寄存器的名字.寄存器名字取決于機器,用機器描述宏文件的REGISTER_NAMES宏 定義.

            這個選項沒有否定格式,因為它列出三路選擇.

             

            -fcall-used-reg
            把名為reg的寄存器按可分配寄存器看待,不能在函數調用間使用.可以臨時使用或當做變量使用,生存期 不超過一個函數.這樣編譯的函數無需保存和恢復reg寄存器.

            如果在可執行模塊中,把這個選項說明的寄存器用作固定角色將會產生災難性結果,如棧指針或幀指針.

            這個選項沒有否定格式,因為它列出三路選擇.

             

            -fcall-saved-reg
            把名為reg的寄存器按函數保護的可分配寄存器看待.可以臨時使用或當做變量使用,它甚至能在函數間 生存.這樣編譯的函數會保存和恢復使用中的reg寄存器.

            如果在可執行模塊中,把這個選項說明的寄存器用作固定角色將會產生災難性結果,如棧指針或幀指針.

            另一種災難是用這個選項說明的寄存器返回函數值.

            這個選項沒有否定格式,因為它列出三路選擇.

             

            PRAGMAS

            GNU C++支持兩條`#pragma'指令使同一個頭文件有兩個用途:對象類的接口定義, 對象類完整的內容定義.

            #pragma interface
            (僅對C++)在定義對象類的頭文件中,使用這個指令可以節省大部分采用該類的目標文件的大小.一般說來,某些信息 (內嵌成員函數的備份副件,調試信息,實現虛函數的內部表格等)的本地副件必須保存在包含類定義的各個目標文件中.使用這個 pragma指令能夠避免這樣的復制.當編譯中引用包含`#pragma interface'指令的頭文件時,就 不會產生這些輔助信息(除非輸入的主文件使用了`#pragma implementation'指令).作為替代,目標文件 將包含可被連接時解析的引用(reference).

             

            #pragma implementation
            #pragma implementation "objects.h"
            (僅對C++)如果要求從頭文件產生完整的輸出(并且全局可見),你應該在主輸入文件中使用這條pragma.頭文件 中應該依次使用`#pragma interface'指令.implementation文件中將產生全部內嵌成員函數 的備份,調試信息,實現虛函數的內部表格等.

            如果`#pragma implementation'不帶參數,它指的是和源文件有相同基本名的包含文件;例如, `allclass.cc', `#pragma implementation'等于`#pragma implementation allclass.h'.如果某個implementation文件需要從多個頭文件引入代碼,就應該 使用這個字符串參數.

            不可能把一個頭文件里面的內容分割到多個implementation文件中.

             

            文件(FILE)

            file.c             C源文件
            file.h             C頭文件(預處理文件)
            file.i            預處理后的C源文件
            file.C             C++源文件
            file.cc            C++源文件
            file.cxx           C++源文件
            file.m             Objective-C源文件
            file.s            匯編語言文件
            file.o            目標文件
            a.out             連接的輸出文件
            TMPDIR/cc*        臨時文件
            LIBDIR/cpp        預處理器
            LIBDIR/cc1         C編譯器
            LIBDIR/cc1plus     C++編譯器
            LIBDIR/collect    某些機器需要的連接器前端(front end)程序
            LIBDIR/libgcc.a    GCC子例程(subroutine)/lib/crt[01n].o   啟動例程(start-up)
            LIBDIR/ccrt0       C++的附加啟動例程
            /lib/libc.a       標準C,另見intro (3)
            /usr/include       #include文件的標準目錄
            LIBDIR/include     #include文件的標準gcc目錄
            LIBDIR/g++-include #include文件的附加g++目錄
            
            LIBDIR通常為/usr/local/lib/machine/version.
            TMPDIR
            來自環境變量TMPDIR (如果存在,缺省為/usr/tmp ,否則為 /tmp).

             

            BUGS

            關于報告差錯的指導請查閱GCC手冊.

            版權(COPYING)

            Copyright 1991, 1992, 1993 Free Software Foundation, Inc.

            Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.

            Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.

            Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be included in translations approved by the Free Software Foundation instead of in the original English.

            作者(AUTHORS)

            關于GNU CC的奉獻者請查閱GUN CC手冊.
            posted @ 2009-08-03 14:32 chaosuper 閱讀(632) | 評論 (0)編輯 收藏

                 摘要: GCC 4 Section: GNU Tools (1)Updated: 2003/12/05 機器相關選項(MACHINE DEPENDENT OPTION) 每一種目標機型都有自己的特別選項,這些選項用`-m '開關引導,選擇不同的硬件型號或配置---例如, 68010還是68020,有沒有浮點協處理器.通過指定選項,安裝 編譯器的一個版本能夠為所有的型號或配置進行編譯. 此外...  閱讀全文
            posted @ 2009-08-03 14:31 chaosuper 閱讀(290) | 評論 (0)編輯 收藏

                 摘要: GCC 3 Section: GNU Tools (1)Updated: 2003/12/05   調試選項(DEBUGGING OPTION) GNU CC擁有許多特別選項,既可以調試用戶的程序,也可以對GCC排錯: -g 以操作系統的本地格式(stabs, COFF, XCOFF,或DWARF).產生調試信息. GDB能夠使用這些調試信息. 在大多...  閱讀全文
            posted @ 2009-08-03 14:31 chaosuper 閱讀(178) | 評論 (0)編輯 收藏

                 摘要: GCC 2 Section: GNU Tools (1)Updated: 2003/12/05 預處理器選項(Preprocessor Option) 下列選項針對C預處理器,預處理器用在正式編譯以前,對C 源文件進行某種處理. 如果指定了`-E'選項, GCC只進行預處理工作.下面的某些選項必須和`-E'選項一起才 有意義,因為他們的輸出結果不能用于編譯. -includ...  閱讀全文
            posted @ 2009-08-03 14:30 chaosuper 閱讀(194) | 評論 (0)編輯 收藏

            GCC 1

            Section: GNU Tools (1)
            Updated: 2003/12/05
            ÆäËûÁ¬½Ó: GNU ¹«¹²ÊÚȨ

            NAME

            gcc,g++-GNU工程的CC++編譯器(egcs-1.1.2)

            總覽(SYNOPSIS)

            gcc[option|filename ]...
            g++[option|filename ]...

            警告(WARNING)

            本手冊頁內容摘自GNU C編譯器的完整文檔,僅限于解釋選項的含義.

            除非有人自愿維護,否則本手冊頁不再更新.如果發現手冊頁和軟件之間有所矛盾,請查對Info文件, Info文件是權威文檔.

            如果我們發覺本手冊頁的內容由于過時而導致明顯的混亂和抱怨時,我們就停止發布它.不可能有其他選擇,象更新Info文件同時更新man手冊,因為其他維護GNU CC的工作沒有留給我們時間做這個. GNU工程認為man手冊是過時產物,應該把時間用到別的地方.

            如果需要完整和最新的文檔,請查閱Info文件`gcc'Using and Porting GNU CC (for version 2.0) (使用和移植GNU CC 2.0) 手冊.二者均來自Texinfo原文件 gcc.texinfo.

            描述(DESCRIPTION)

            CC++編譯器是集成的.他們都要用四個步驟中的一個或多個處理輸入文件: 預處理(preprocessing),編譯(compilation),匯編(assembly)和連接(linking).源文件后綴名標識源文件的 語言,但是對編譯器來說,后綴名控制著缺省設定:
            gcc
            認為預處理后的文件(.i)C文件,并且設定C形式的連接.
            g++
            認為預處理后的文件(.i)C++文件,并且設定C++形式的連接.

            源文件后綴名指出語言種類以及后期的操作:

            .c      C源程序;預處理,編譯,匯編
            .C      C++源程序;預處理,編譯,匯編
            .cc     C++源程序;預處理,編譯,匯編
            .cxx    C++源程序;預處理,編譯,匯編
            .m      Objective-C源程序;預處理,編譯,匯編
            .i     預處理后的C文件;編譯,匯編
            .ii    預處理后的C++文件;編譯,匯編
            .s     匯編語言源程序;匯編
            .S     匯編語言源程序;預處理,匯編
            .h     預處理器文件;通常不出現在命令行上
            
            其他后綴名的文件被傳遞給連接器(linker).通常包括:

            .o     目標文件(Object file)
            .a     歸檔庫文件(Archive file)
            

            除非使用了-c, -S,-E選項(或者編譯錯誤阻止了完整的過程),否則連接總是 最后的步驟.在連接階段中,所有對應于源程序的.o文件, -l庫文件,無法識別的文件名(包括指定的 .o目標文件和.a庫文件)按命令行中的順序傳遞給連接器.

            選項(OPTIONS)

            選項必須分立給出: `-dr'完全不同于`-d -r '.

            大多數`-f'`-W'選項有兩個相反的格式: -fname-fno-name (-Wname-Wno-name).這里 只列舉不是默認選項的格式.

            下面是所有選項的摘要,按類型分組,解釋放在后面的章節中.

            總體選項(Overall Option)

            -c -S -E -o file -pipe -v -x language
            語言選項(Language Option)
            -ansi -fall-virtual -fcond-mismatch -fdollars-in-identifiers -fenum-int-equiv -fexternal-templates -fno-asm -fno-builtin -fhosted -fno-hosted -ffreestanding -fno-freestanding -fno-strict-prototype -fsigned-bitfields -fsigned-char -fthis-is-variable -funsigned-bitfields -funsigned-char -fwritable-strings -traditional -traditional-cpp -trigraphs
            警告選項(Warning Option)
            -fsyntax-only -pedantic -pedantic-errors -w -W -Wall -Waggregate-return -Wcast-align -Wcast-qual -Wchar-subscript -Wcomment -Wconversion -Wenum-clash -Werror -Wformat -Wid-clash-len -Wimplicit -Wimplicit-int -Wimplicit-function-declaration -Winline -Wlong-long -Wmain -Wmissing-prototypes -Wmissing-declarations -Wnested-externs -Wno-import -Wparentheses -Wpointer-arith -Wredundant-decls -Wreturn-type -Wshadow -Wstrict-prototypes -Wswitch -Wtemplate-debugging -Wtraditional -Wtrigraphs -Wuninitialized -Wunused -Wwrite-strings
            調試選項(Debugging Option)
            -a -dletters -fpretend-float -g -glevel -gcoff -gxcoff -gxcoff+ -gdwarf -gdwarf+ -gstabs -gstabs+ -ggdb -p -pg -save-temps -print-file-name=library -print-libgcc-file-name -print-prog-name=program
            優化選項(Optimization Option)
            -fcaller-saves -fcse-follow-jumps -fcse-skip-blocks -fdelayed-branch -felide-constructors -fexpensive-optimizations -ffast-math -ffloat-store -fforce-addr -fforce-mem -finline-functions -fkeep-inline-functions -fmemoize-lookups -fno-default-inline -fno-defer-pop -fno-function-cse -fno-inline -fno-peephole -fomit-frame-pointer -frerun-cse-after-loop -fschedule-insns -fschedule-insns2 -fstrength-reduce -fthread-jumps -funroll-all-loops -funroll-loops -O -O2 -O3
            預處理器選項(Preprocessor Option)
            -Aassertion -C -dD -dM -dN -Dmacro[=defn] -E -H -idirafter dir -include file -imacros file -iprefix file -iwithprefix dir -M -MD -MM -MMD -nostdinc -P -Umacro -undef
            匯編器選項(Assembler Option)
            -Wa,option
            連接器選項(Linker Option)
            -llibrary -nostartfiles -nostdlib -static -shared -symbolic -Xlinker option -Wl,option -u symbol
            目錄選項(Directory Option)
            -Bprefix -Idir -I- -Ldir
            目標機選項(Target Option)
            -b machine -V version
            配置相關選項(Configuration Dependent Option)
            M680x0 選項
            -m68000 -m68020 -m68020-40 -m68030 -m68040 -m68881 -mbitfield -mc68000 -mc68020 -mfpa -mnobitfield -mrtd -mshort -msoft-float

            VAX選項
            -mg -mgnu -munix

            SPARC選項
            -mepilogue -mfpu -mhard-float -mno-fpu -mno-epilogue -msoft-float -msparclite -mv8 -msupersparc -mcypress

            Convex選項
            -margcount -mc1 -mc2 -mnoargcount

            AMD29K選項
            -m29000 -m29050 -mbw -mdw -mkernel-registers -mlarge -mnbw -mnodw -msmall -mstack-check -muser-registers

            M88K選項
            -m88000 -m88100 -m88110 -mbig-pic -mcheck-zero-division -mhandle-large-shift -midentify-revision -mno-check-zero-division -mno-ocs-debug-info -mno-ocs-frame-position -mno-optimize-arg-area -mno-serialize-volatile -mno-underscores -mocs-debug-info -mocs-frame-position -moptimize-arg-area -mserialize-volatile -mshort-data-num -msvr3 -msvr4 -mtrap-large-shift -muse-div-instruction -mversion-03.00 -mwarn-passed-structs

            RS6000選項
            -mfp-in-toc -mno-fop-in-toc

            RT選項
            -mcall-lib-mul -mfp-arg-in-fpregs -mfp-arg-in-gregs -mfull-fp-blocks -mhc-struct-return -min-line-mul -mminimum-fp-blocks -mnohc-struct-return

            MIPS選項
            -mcpu=cpu type -mips2 -mips3 -mint64 -mlong64 -mmips-as -mgas -mrnames -mno-rnames -mgpopt -mno-gpopt -mstats -mno-stats -mmemcpy -mno-memcpy -mno-mips-tfile -mmips-tfile -msoft-float -mhard-float -mabicalls -mno-abicalls -mhalf-pic -mno-half-pic -G num -nocpp

            i386選項
            -m486 -mno-486 -msoft-float -mno-fp-ret-in-387

            HPPA選項
            -mpa-risc-1-0 -mpa-risc-1-1 -mkernel -mshared-libs -mno-shared-libs -mlong-calls -mdisable-fpregs -mdisable-indexing -mtrailing-colon

            i960選項
            -mcpu-type -mnumerics -msoft-float -mleaf-procedures -mno-leaf-procedures -mtail-call -mno-tail-call -mcomplex-addr -mno-complex-addr -mcode-align -mno-code-align -mic-compat -mic2.0-compat -mic3.0-compat -masm-compat -mintel-asm -mstrict-align -mno-strict-align -mold-align -mno-old-align

            DEC Alpha選項
            -mfp-regs -mno-fp-regs -mno-soft-float -msoft-float

            System V選項
            -G -Qy -Qn -YP,paths -Ym,dir

            代碼生成選項(Code Generation Option)
            -fcall-saved-reg -fcall-used-reg -ffixed-reg -finhibit-size-directive -fnonnull-objects -fno-common -fno-ident -fno-gnu-linker -fpcc-struct-return -fpic -fPIC -freg-struct-return -fshared-data -fshort-enums -fshort-double -fvolatile -fvolatile-global -fverbose-asm

             

            總體選項(Overall Option)

            -x language
            明確指出后面輸入文件的語言為language (而不是從文件名后綴得到的默認選擇).這個選項應用于后面 所有的輸入文件,直到遇著下一個`-x'選項. language的可選值有`c', `objective-c', `c-header', `c++', `cpp-output', `assembler',`assembler-with-cpp'.
            -x none
            關閉任何對語種的明確說明,因此依據文件名后綴處理后面的文件(就象是從未使用過`-x'選項).

            如果只操作四個階段(預處理,編譯,匯編,連接)中的一部分,可以使用`-x'選項(或文件名后綴)告訴 gcc從哪里開始,`-c', `-S',`-E'選項告訴gcc到 哪里結束.注意,某些選項組合(例如, `-x cpp-output -E')使gcc不作任何事情.

            -c
            編譯或匯編源文件,但是不作連接.編譯器輸出對應于源文件的目標文件.

            缺省情況下, GCC通過用`.o'替換源文件名后綴`.c', `.i', `.s',等等,產生目標文件名.可以使用-o選項選擇其他名字.

            GCC忽略-c選項后面任何無法識別的輸入文件(他們不需要編譯或匯編).

            -S
            編譯后即停止,不進行匯編.對于每個輸入的非匯編語言文件,輸出文件是匯編語言文件.

            缺省情況下, GCC通過用`.o'替換源文件名后綴`.c', `.i',等等,產生 目標文件名.可以使用-o選項選擇其他名字.

            GCC忽略任何不需要編譯的輸入文件.

            -E
            預處理后即停止,不進行編譯.預處理后的代碼送往標準輸出.

            GCC忽略任何不需要預處理的輸入文件.

            -o file
            指定輸出文件為file.該選項不在乎GCC產生什么輸出,無論是可執行文件,目標文件,匯編文件還是 預處理后的C代碼.

            由于只能指定一個輸出文件,因此編譯多個輸入文件時,使用`-o'選項沒有意義,除非輸出一個可執行文件.

            如果沒有使用`-o'選項,默認的輸出結果是:可執行文件為`a.out', `source.suffix '的目標文件是`source.o',匯編文件是 `source.s',而預處理后的C源代碼送往標準輸出.

            -v
            (在標準錯誤)顯示執行編譯階段的命令.同時顯示編譯器驅動程序,預處理器,編譯器的版本號.
            -pipe
            在編譯過程的不同階段間使用管道而非臨時文件進行通信.這個選項在某些系統上無法工作,因為那些系統的 匯編器不能從管道讀取數據. GNU的匯編器沒有這個問題.

            語言選項(LANGUAGE OPTIONS)

            下列選項控制編譯器能夠接受的C "方言":
            -ansi
            支持符合ANSI標準的C程序.

            這樣就會關閉GNU C中某些不兼容ANSI C的特性,例如asm, inlinetypeof關鍵字,以及諸如unixvax這些表明當前系統類型的預定義宏.同時開啟 不受歡迎和極少使用的ANSI trigraph特性,以及禁止`$'成為標識符的一部分.

            盡管使用了`-ansi'選項,下面這些可選的關鍵字, __asm__, __extension__, __inline____typeof__仍然有效.你當然不會把 他們用在ANSI C程序中,但可以把他們放在頭文件里,因為編譯包含這些頭文件的程序時,可能會指定 `-ansi'選項.另外一些預定義宏,__unix____vax__,無論有沒有使用 `-ansi'選項,始終有效.

            使用`-ansi'選項不會自動拒絕編譯非ANSI程序,除非增加`-pedantic'選項作為 `-ansi'選項的補充.

            使用`-ansi'選項的時候,預處理器會預定義一個__STRICT_ANSI__.有些頭文件 關注此宏,以避免聲明某些函數,或者避免定義某些宏,這些函數和宏不被ANSI標準調用;這樣就不會干擾在其他地方 使用這些名字的程序了.

             

            -fno-asm
            不把asm, inlinetypeof當作關鍵字,因此這些詞可以用做標識符.__asm__, __inline____typeof__能夠替代他們. `-ansi' 隱含聲明了`-fno-asm'.
            -fno-builtin
            不接受不是兩個下劃線開頭的內建函數(built-in function).目前受影響的函數有_exit, abort, abs, alloca, cos, exit, fabs, labs, memcmp, memcpy, sin, sqrt, strcmp, strcpy,strlen.

            `-ansi'選項能夠阻止alloca_exit成為內建函數.

             

            -fhosted
            按宿主環境編譯;他隱含聲明了`-fbuiltin'選項,而且警告不正確的main函數聲明.
            -ffreestanding
            按獨立環境編譯;他隱含聲明了`-fno-builtin'選項,而且對main函數沒有特別要求.

            (譯注:宿主環境(hosted environment)下所有的標準庫可用, main函數返回一個int,典型例子是除了 內核以外幾乎所有的程序.對應的獨立環境(freestanding environment)不存在標準庫,程序入口也不一定是 main,最明顯的例子就是操作系統內核.詳情參考gcc網站最近的資料)

             

            -fno-strict-prototype
            對于沒有參數的函數聲明,例如`int foo ();',C風格處理---即不說明參數個數或類型. (僅針對C++).正常情況下,這樣的函數fooC++中意味著參數為空.

             

            -trigraphs
            支持ANSI C trigraphs. `-ansi'選項隱含聲明了`-trigraphs'.

             

            -traditional
            試圖支持傳統C編譯器的某些方面.詳見GNU C手冊,我們已經把細節清單從這里刪除,這樣當內容過時后,人們也不會 埋怨我們.

            除了一件事:對于C++程序(不是C), `-traditional'選項帶來一個附加效應,允許對 this賦值.他和`-fthis-is-variable'選項的效果一樣.

             

            -traditional-cpp
            試圖支持傳統C預處理器的某些方面.特別是上面提到有關預處理器的內容,但是不包括 `-traditional'選項的其他效應.

             

            -fdollars-in-identifiers
            允許在標識符(identifier)中使用`$'字符(僅針對C++).你可以指定 `-fno-dollars-in-identifiers'選項顯明禁止使用`$'. (GNU C++在某些 目標系統缺省允許`$',但不是所有系統.)

             

            -fenum-int-equiv
            允許int類型到枚舉類型(enumeration)的隱式轉換(僅限于C++).正常情況下GNU C++允許從 enumint的轉換,反之則不行.

             

            -fexternal-templates
            為模板聲明(template declaration)產生較小的代碼(僅限于C++),方法是對于每個模板函數 (template function),只在定義他們的地方生成一個副本.想要成功使用這個選項,你必須在所有使用模板的 文件中,標記`#pragma implementation' (定義)`#pragma interface' (聲明).

            當程序用`-fexternal-templates'編譯時,模板實例(template instantiation) 全部是外部類型.你必須讓需要的實例在實現文件中出現.可以通過typedef實現這一點,他引用所需的每個 實例.相對應的,如果編譯時使用缺省選項`-fno-external-templates',所有模板實例明確的設為內置.

             

            -fall-virtual
            所有可能的成員函數默認為虛函數.所有的成員函數(除了構造子函數和newdelete 成員操作符)視為所在類的虛函數.

            這不表明每次調用成員函數都將通過內部虛函數表.有些情況下,編譯器能夠判斷出可以直接調用某個虛函數;這時就 直接調用.

             

            -fcond-mismatch
            允許條件表達式的第二和第三個參數的類型不匹配.這種表達式的值是void.

             

            -fthis-is-variable
            允許對this賦值(僅對C++).合并用戶自定義的自由存儲管理機制到C++,使可賦值的 `this'顯得不合時宜.因此,默認情況下,類成員函數內部對this賦值是無效操作.然而為了 向后兼容,你可以通過`-fthis-is-variable'選項使這種操作有效.

             

            -funsigned-char
            char定義為無符號類型,如同unsigned char.

            各種機器都有自己缺省的char類型.既可能是unsigned char也可能是signed char .

            理想情況下,當依賴于數據的符號性時,一個可移植程序總是應該使用signed charunsigned char.但是許多程序已經寫成只用簡單的char,并且期待這是有符號數(或者無符號數,具體情況取決于 編寫程序的目標機器).這個選項,和它的反義選項,使那樣的程序工作在對應的默認值上.

            char的類型始終應該明確定義為signed charunsigned char,即使 它表現的和其中之一完全一樣.

             

            -fsigned-char
            char定義為有符號類型,如同signed char.

            這個選項等同于`-fno-unsigned-char',他是the negative form of `-funsigned-char'的相反選項.同樣, `-fno-signed-char'等價于 `-funsigned-char'.

             

            -fsigned-bitfields
            -funsigned-bitfields
            -fno-signed-bitfields
            -fno-unsigned-bitfields
            如果沒有明確聲明`signed'`unsigned'修飾符,這些選項用來定義有符號位域 (bitfield)或無符號位域.缺省情況下,位域是有符號的,因為他們繼承的基本整數類型,int,是 有符號數.

            然而,如果指定了`-traditional'選項,位域永遠是無符號數.

             

            -fwritable-strings
            把字符串常量存儲到可寫數據段,而且不做特別對待.這是為了兼容一些老程序,他們假設字符串常量是可寫的. `-traditional'選項也有相同效果.

            篡改字符串常量是一個非常糟糕的想法; ``常量''就應該是常量.

            posted @ 2009-08-03 14:30 chaosuper 閱讀(196) | 評論 (0)編輯 收藏

            0 Makefile概述


            什么是makefile?或許很多Winodws的程序員都不知道這個東西,因為那些Windows的IDE都為你做了這個工作,但我覺得要作一個好的和professional的程序員,makefile還是要懂。這就好像現在有這么多的HTML的編輯器,但如果你想成為一個專業人士,你還是要了解HTML的標識的含義。特別在Unix下的軟件編譯,你就不能不自己寫makefile了,會不會寫makefile,從一個側面說明了一個人是否具備完成大型工程的能力。

            因為,makefile關系到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進行更復雜的功能操作,因為makefile就像一個Shell腳本一樣,其中也可以執行操作系統的命令。

            makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟件開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數的IDE都有這個命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成為了一種在工程方面的編譯方法。

            現在講述如何寫makefile的文章比較少,這是我想寫這篇文章的原因。當然,不同產商的make各不相同,也有不同的語法,但其本質都是在“文件依賴性”上做文章,這里,我僅對GNU的make進行講述,我的環境是RedHat Linux 8.0,make的版本是3.80。必竟,這個make是應用最為廣泛的,也是用得最多的。而且其還是最遵循于IEEE 1003.2-1992 標準的(POSIX.2)。

            在這篇文檔中,將以C/C++的源碼作為我們基礎,所以必然涉及一些關于C/C++的編譯的知識,相關于這方面的內容,還請各位查看相關的編譯器的文檔。這里所默認的編譯器是UNIX下的GCC和CC。

            0.1 關于程序的編譯和鏈接

            在此,我想多說關于程序編譯的一些規范和方法,一般來說,無論是C、C++、還是pas,首先要把源文件編譯成中間代碼文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,這個動作叫做編譯(compile)。然后再把大量的Object File合成執行文件,這個動作叫作鏈接(link)。

            編譯時,編譯器需要的是語法的正確,函數與變量的聲明的正確。對于后者,通常是你需要告訴編譯器頭文件的所在位置(頭文件中應該只是聲明,而定義應該放在C/C++文件中),只要所有的語法正確,編譯器就可以編譯出中間目標文件。一般來說,每個源文件都應該對應于一個中間目標文件(O文件或是OBJ文件)。

            鏈接時,主要是鏈接函數和全局變量,所以,我們可以使用這些中間目標文件(O文件或是OBJ文件)來鏈接我們的應用程序。鏈接器并不管函數所在的源文件,只管函數的中間目標文件(Object File),在大多數時候,由于源文件太多,編譯生成的中間目標文件太多,而在鏈接時需要明顯地指出中間目標文件名,這對于編譯很不方便,所以,我們要給中間目標文件打個包,在Windows下這種包叫“庫文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

            總結一下,源文件首先會生成中間目標文件,再由中間目標文件生成執行文件。在編譯時,編譯器只檢測程序語法,和函數、變量是否被聲明。如果函數未被聲明,編譯器會給出一個警告,但可以生成Object File。而在鏈接程序時,鏈接器會在所有的Object File中找尋函數的實現,如果找不到,那到就會報鏈接錯誤碼(Linker Error),在VC下,這種錯誤一般是:Link 2001錯誤,意思說是說,鏈接器未能找到函數的實現。你需要指定函數的Object File.

            好,言歸正傳,GNU的make有許多的內容,閑言少敘,還是讓我們開始吧。

            1 Makefile 介紹


            make命令執行時,需要一個 Makefile 文件,以告訴make命令需要怎么樣的去編譯和鏈接程序。

            首先,我們用一個示例來說明Makefile的書寫規則。以便給大家一個感興認識。這個示例來源于GNU的make使用手冊,在這個示例中,我們的工程有8個C文件,和3個頭文件,我們要寫一個Makefile來告訴make命令如何編譯和鏈接這幾個文件。我們的規則是:

            1. 如果這個工程沒有編譯過,那么我們的所有C文件都要編譯并被鏈接。
            2. 如果這個工程的某幾個C文件被修改,那么我們只編譯被修改的C文件,并鏈接目標程序。
            3. 如果這個工程的頭文件被改變了,那么我們需要編譯引用了這幾個頭文件的C文件,并鏈接目標程序。

            只要我們的Makefile寫得夠好,所有的這一切,我們只用一個make命令就可以完成,make命令會自動智能地根據當前的文件修改的情況來確定哪些文件需要重編譯,從而自己編譯所需要的文件和鏈接目標程序。

            1.1 Makefile的規則

            在講述這個Makefile之前,還是讓我們先來粗略地看一看Makefile的規則。
            target ... : prerequisites ...
            command
            ...
            ...
            
            target也就是一個目標文件,可以是Object File,也可以是執行文件。還可以是一個標簽(Label),對于標簽這種特性,在后續的“偽目標”章節中會有敘述。

            prerequisites就是,要生成那個target所需要的文件或是目標。

            command也就是make需要執行的命令。(任意的Shell命令)

            這是一個文件的依賴關系,也就是說,target這一個或多個的目標文件依賴于prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容。

            說到底,Makefile的東西就是這樣一點,好像我的這篇文檔也該結束了。呵呵。還不盡然,這是Makefile的主線和核心,但要寫好一個Makefile還不夠,我會以后面一點一點地結合我的工作經驗給你慢慢到來。內容還多著呢。:)

            1.2 一個示例

            正如前面所說的,如果一個工程有3個頭文件,和8個C文件,我們為了完成前面所述的那三個規則,我們的Makefile應該是下面的這個樣子的。
                edit : main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            cc -o edit main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            main.o : main.c defs.h
            cc -c main.c
            kbd.o : kbd.c defs.h command.h
            cc -c kbd.c
            command.o : command.c defs.h command.h
            cc -c command.c
            display.o : display.c defs.h buffer.h
            cc -c display.c
            insert.o : insert.c defs.h buffer.h
            cc -c insert.c
            search.o : search.c defs.h buffer.h
            cc -c search.c
            files.o : files.c defs.h buffer.h command.h
            cc -c files.c
            utils.o : utils.c defs.h
            cc -c utils.c
            clean :
            rm edit main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            
            反斜杠(\)是換行符的意思。這樣比較便于Makefile的易讀。我們可以把這個內容保存在文件為“Makefile”或“makefile”的文件中,然后在該目錄下直接輸入命令“make”就可以生成執行文件edit。如果要刪除執行文件和所有的中間目標文件,那么,只要簡單地執行一下“make clean”就可以了。

            在這個makefile中,目標文件(target)包含:執行文件edit和中間目標文件(*.o),依賴文件(prerequisites)就是冒號后面的那些 .c 文件和 .h文件。每一個 .o 文件都有一組依賴文件,而這些 .o 文件又是執行文件 edit 的依賴文件。依賴關系的實質上就是說明了目標文件是由哪些文件生成的,換言之,目標文件是哪些文件更新的。

            在定義好依賴關系后,后續的那一行定義了如何生成目標文件的操作系統命令,一定要以一個Tab鍵作為開頭。記住,make并不管命令是怎么工作的,他只管執行所定義的命令。make會比較targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的話,那么,make就會執行后續定義的命令。

            這里要說明一點的是,clean不是一個文件,它只不過是一個動作名字,有點像C語言中的lable一樣,其冒號后什么也沒有,那么,make就不會自動去找文件的依賴性,也就不會自動執行其后所定義的命令。要執行其后的命令,就要在make命令后明顯得指出這個lable的名字。這樣的方法非常有用,我們可以在一個makefile中定義不用的編譯或是和編譯無關的命令,比如程序的打包,程序的備份,等等。

            1.3 make是如何工作的

            在默認的方式下,也就是我們只輸入make命令。那么,

            1. make會在當前目錄下找名字叫“Makefile”或“makefile”的文件。
            2. 如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“edit”這個文件,并把這個文件作為最終的目標文件。
            3. 如果edit文件不存在,或是edit所依賴的后面的 .o 文件的文件修改時間要比edit這個文件新,那么,他就會執行后面所定義的命令來生成edit這個文件。
            4. 如果edit所依賴的.o文件也存在,那么make會在當前文件中找目標為.o文件的依賴性,如果找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程)
            5. 當然,你的C文件和H文件是存在的啦,于是make會生成 .o 文件,然后再用 .o 文件生命make的終極任務,也就是執行文件edit了。

            這就是整個make的依賴性,make會一層又一層地去找文件的依賴關系,直到最終編譯出第一個目標文件。在找尋的過程中,如果出現錯誤,比如最后被依賴的文件找不到,那么make就會直接退出,并報錯,而對于所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即,如果在我找了依賴關系之后,冒號后面的文件還是不在,那么對不起,我就不工作啦。

            通過上述分析,我們知道,像clean這種,沒有被第一個目標文件直接或間接關聯,那么它后面所定義的命令將不會被自動執行,不過,我們可以顯示要make執行。即命令——“make clean”,以此來清除所有的目標文件,以便重編譯。

            于是在我們編程中,如果這個工程已被編譯過了,當我們修改了其中一個源文件,比如file.c,那么根據我們的依賴性,我們的目標file.o會被重編譯(也就是在這個依性關系后面所定義的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改時間要比edit要新,所以edit也會被重新鏈接了(詳見edit目標文件后定義的命令)。

            而如果我們改變了“command.h”,那么,kdb.o、command.o和files.o都會被重編譯,并且,edit會被重鏈接。

            1.4 makefile中使用變量

            在上面的例子中,先讓我們看看edit的規則:
                  edit : main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            cc -o edit main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            
            我們可以看到[.o]文件的字符串被重復了兩次,如果我們的工程需要加入一個新的[.o]文件,那么我們需要在兩個地方加(應該是三個地方,還有一個地方在clean中)。當然,我們的makefile并不復雜,所以在兩個地方加也不累,但如果makefile變得復雜,那么我們就有可能會忘掉一個需要加入的地方,而導致編譯失敗。所以,為了makefile的易維護,在makefile中我們可以使用變量。makefile的變量也就是一個字符串,理解成C語言中的宏可能會更好。

            比如,我們聲明一個變量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能夠表示obj文件就行了。我們在makefile一開始就這樣定義:

                 objects = main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            
            于是,我們就可以很方便地在我們的makefile中以“$(objects)”的方式來使用這個變量了,于是我們的改良版makefile就變成下面這個樣子:
                objects = main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            edit : $(objects)
            cc -o edit $(objects)
            main.o : main.c defs.h
            cc -c main.c
            kbd.o : kbd.c defs.h command.h
            cc -c kbd.c
            command.o : command.c defs.h command.h
            cc -c command.c
            display.o : display.c defs.h buffer.h
            cc -c display.c
            insert.o : insert.c defs.h buffer.h
            cc -c insert.c
            search.o : search.c defs.h buffer.h
            cc -c search.c
            files.o : files.c defs.h buffer.h command.h
            cc -c files.c
            utils.o : utils.c defs.h
            cc -c utils.c
            clean :
            rm edit $(objects)
            

            于是如果有新的 .o 文件加入,我們只需簡單地修改一下 objects 變量就可以了。

            關于變量更多的話題,我會在后續給你一一道來。

            1.5 讓make自動推導

            GNU的make很強大,它可以自動推導文件以及文件依賴關系后面的命令,于是我們就沒必要去在每一個[.o]文件后都寫上類似的命令,因為,我們的make會自動識別,并自己推導命令。

            只要make看到一個[.o]文件,它就會自動的把[.c]文件加在依賴關系中,如果make找到一個whatever.o,那么whatever.c,就會是whatever.o的依賴文件。并且 cc -c whatever.c 也會被推導出來,于是,我們的makefile再也不用寫得這么復雜。我們的是新的makefile又出爐了。

                objects = main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            edit : $(objects)
            cc -o edit $(objects)
            main.o : defs.h
            kbd.o : defs.h command.h
            command.o : defs.h command.h
            display.o : defs.h buffer.h
            insert.o : defs.h buffer.h
            search.o : defs.h buffer.h
            files.o : defs.h buffer.h command.h
            utils.o : defs.h
            .PHONY : clean
            clean :
            rm edit $(objects)
            
            這種方法,也就是make的“隱晦規則”。上面文件內容中,“.PHONY”表示,clean是個偽目標文件。

            關于更為詳細的“隱晦規則”和“偽目標文件”,我會在后續給你一一道來。

            1.6 另類風格的makefile

            即然我們的make可以自動推導命令,那么我看到那堆[.o]和[.h]的依賴就有點不爽,那么多的重復的[.h],能不能把其收攏起來,好吧,沒有問題,這個對于make來說很容易,誰叫它提供了自動推導命令和文件的功能呢?來看看最新風格的makefile吧。
                objects = main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
            edit : $(objects)
            cc -o edit $(objects)
            $(objects) : defs.h
            kbd.o command.o files.o : command.h
            display.o insert.o search.o files.o : buffer.h
            .PHONY : clean
            clean :
            rm edit $(objects)
            
            這種風格,讓我們的makefile變得很簡單,但我們的文件依賴關系就顯得有點凌亂了。魚和熊掌不可兼得。還看你的喜好了。我是不喜歡這種風格的,一是文件的依賴關系看不清楚,二是如果文件一多,要加入幾個新的.o文件,那就理不清楚了。

            1.7 清空目標文件的規則

            每個Makefile中都應該寫一個清空目標文件(.o和執行文件)的規則,這不僅便于重編譯,也很利于保持文件的清潔。這是一個“修養”(呵呵,還記得我的《編程修養》嗎)。一般的風格都是:
                    clean:
            rm edit $(objects)
            
            更為穩健的做法是:
                    .PHONY : clean
            clean :
            -rm edit $(objects)
            
            前面說過,.PHONY意思表示clean是一個“偽目標”,。而在rm命令前面加了一個小減號的意思就是,也許某些文件出現問題,但不要管,繼續做后面的事。當然,clean的規則不要放在文件的開頭,不然,這就會變成make的默認目標,相信誰也不愿意這樣。不成文的規矩是——“clean從來都是放在文件的最后”。

            上面就是一個makefile的概貌,也是makefile的基礎,下面還有很多makefile的相關細節,準備好了嗎?準備好了就來。


            2 Makefile 總述

            2.1 Makefile里有什么?

            Makefile里主要包含了五個東西:顯式規則、隱晦規則、變量定義、文件指示和注釋。

            1. 顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。
            2. 隱晦規則。由于我們的make有自動推導的功能,所以隱晦的規則可以讓我們比較粗糙地簡略地書寫Makefile,這是由make所支持的。
            3. 變量的定義。在Makefile中我們要定義一系列的變量,變量一般都是字符串,這個有點你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
            4. 文件指示。其包括了三個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣;另一個是指根據某些情況指定Makefile中的有效部分,就像C語言中的預編譯#if一樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在后續的部分中講述。
            5. 注釋。Makefile中只有行注釋,和UNIX的Shell腳本一樣,其注釋是用“#”字符,這個就像C/C++中的“//”一樣。如果你要在你的Makefile中使用“#”字符,可以用反斜框進行轉義,如:“\#”。

            最后,還值得一提的是,在Makefile中的命令,必須要以[Tab]鍵開始。

            2.2Makefile的文件名

            默認的情況下,make命令會在當前目錄下按順序找尋文件名為“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解釋這個文件。在這三個文件名中,最好使用“Makefile”這個文件名,因為,這個文件名第一個字符為大寫,這樣有一種顯目的感覺。最好不要用“GNUmakefile”,這個文件是GNU的make識別的。有另外一些make只對全小寫的“makefile”文件名敏感,但是基本上來說,大多數的make都支持“makefile”和“Makefile”這兩種默認文件名。

            當然,你可以使用別的文件名來書寫Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”參數,如:make -f Make.Linux或make --file Make.AIX。

            2.3 引用其它的Makefile

            在Makefile使用include關鍵字可以把別的Makefile包含進來,這很像C語言的#include,被包含的文件會原模原樣的放在當前文件的包含位置。include的語法是:
            include <filename>
            filename可以是當前操作系統Shell的文件模式(可以保含路徑和通配符)

            在include前面可以有一些空字符,但是絕不能是[Tab]鍵開始。include和可以用一個或多個空格隔開。舉個例子,你有這樣幾個Makefile:a.mk、b.mk、c.mk,還有一個文件叫foo.make,以及一個變量$(bar),其包含了e.mk和f.mk,那么,下面的語句:

                include foo.make *.mk $(bar)
            
            等價于:
                include foo.make a.mk b.mk c.mk e.mk f.mk
            
            make命令開始時,會把找尋include所指出的其它Makefile,并把其內容安置在當前的位置。就好像C/C++的#include指令一樣。如果文件都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,如果當前目錄下沒有找到,那么,make還會在下面的幾個目錄下找:

            1. 如果make執行時,有“-I”或“--include-dir”參數,那么make就會在這個參數所指定的目錄下去尋找。
            2. 如果目錄 /include(一般是:/usr/local/bin或/usr/include)存在的話,make也會去找。

            如果有文件沒有找到的話,make會生成一條警告信息,但不會馬上出現致命錯誤。它會繼續載入其它的文件,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的文件,如果還是不行,make才會出現一條致命信息。如果你想讓make不理那些無法讀取的文件,而繼續執行,你可以在include前加一個減號“-”。如:

            -include <filename>
            其表示,無論include過程中出現什么錯誤,都不要報錯繼續執行。和其它版本make兼容的相關命令是sinclude,其作用和這一個是一樣的。

            2.4 環境變量 MAKEFILES

            如果你的當前環境中定義了環境變量MAKEFILES,那么,make會把這個變量中的值做一個類似于include的動作。這個變量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,從這個環境變中引入的Makefile的“目標”不會起作用,如果環境變量中定義的文件發現錯誤,make也會不理。

            但是在這里我還是建議不要使用這個環境變量,因為只要這個變量一被定義,那么當你使用make時,所有的Makefile都會受到它的影響,這絕不是你想看到的。在這里提這個事,只是為了告訴大家,也許有時候你的Makefile出現了怪事,那么你可以看看當前環境中有沒有定義這個變量。

            2.5 make的工作方式

            GNU的make工作時的執行步驟入下:(想來其它的make也是類似)

            1. 讀入所有的Makefile。
            2. 讀入被include的其它Makefile。
            3. 初始化文件中的變量。
            4. 推導隱晦規則,并分析所有規則。
            5. 為所有的目標文件創建依賴關系鏈。
            6. 根據依賴關系,決定哪些目標要重新生成。
            7. 執行生成命令。

            1-5步為第一個階段,6-7為第二個階段。第一個階段中,如果定義的變量被使用了,那么,make會把其展開在使用的位置。但make并不會完全馬上展開,make使用的是拖延戰術,如果變量出現在依賴關系的規則中,那么僅當這條依賴被決定要使用了,變量才會在其內部展開。

            當然,這個工作方式你不一定要清楚,但是知道這個方式你也會對make更為熟悉。有了這個基礎,后續部分也就容易看懂了。

            3 Makefile書寫規則


            規則包含兩個部分,一個是依賴關系,一個是生成目標的方法。

            在Makefile中,規則的順序是很重要的,因為,Makefile中只應該有一個最終目標,其它的目標都是被這個目標所連帶出來的,所以一定要讓make知道你的最終目標是什么。一般來說,定義在Makefile中的目標可能會有很多,但是第一條規則中的目標將被確立為最終的目標。如果第一條規則中的目標有很多個,那么,第一個目標會成為最終的目標。make所完成的也就是這個目標。

            好了,還是讓我們來看一看如何書寫規則。

            3.1 規則舉例

             foo.o : foo.c defs.h       # foo模塊
            cc -c -g foo.c
            
            看到這個例子,各位應該不是很陌生了,前面也已說過,foo.o是我們的目標,foo.c和defs.h是目標所依賴的源文件,而只有一個命令“cc -c -g foo.c”(以Tab鍵開頭)。這個規則告訴我們兩件事:

            1. 文件的依賴關系,foo.o依賴于foo.c和defs.h的文件,如果foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那么依賴關系發生。
            2. 如果生成(或更新)foo.o文件。也就是那個cc命令,其說明了,如何生成foo.o這個文件。(當然foo.c文件include了defs.h文件)

            3.2 規則的語法

                  targets : prerequisites
            command
            ...
            
            或是這樣:
                  targets : prerequisites ; command
            command
            ...
            
            targets是文件名,以空格分開,可以使用通配符。一般來說,我們的目標基本上是一個文件,但也有可能是多個文件。

            command是命令行,如果其不與“target:prerequisites”在一行,那么,必須以[Tab鍵]開頭,如果和prerequisites在一行,那么可以用分號做為分隔。(見上)

            prerequisites也就是目標所依賴的文件(或依賴目標)。如果其中的某個文件要比目標文件要新,那么,目標就被認為是“過時的”,被認為是需要重生成的。這個在前面已經講過了。

            如果命令太長,你可以使用反斜框(‘\’)作為換行符。make對一行上有多少個字符沒有限制。規則告訴make兩件事,文件的依賴關系和如何成成目標文件。

            一般來說,make會以UNIX的標準Shell,也就是/bin/sh來執行命令。

            3.3 在規則中使用通配符

            如果我們想定義一系列比較類似的文件,我們很自然地就想起使用通配符。make支持三各通配符:“*”,“?”和“[...]”。這是和Unix的B-Shell是相同的。

            波浪號(“~”)字符在文件名中也有比較特殊的用途。如果是“~/test”,這就表示當前用戶的$HOME目錄下的test目錄。而“~hchen/test”則表示用戶hchen的宿主目錄下的test目錄。(這些都是Unix下的小知識了,make也支持)而在Windows或是MS-DOS下,用戶沒有宿主目錄,那么波浪號所指的目錄則根據環境變量“HOME”而定。

            通配符代替了你一系列的文件,如“*.c”表示所以后綴為c的文件。一個需要我們注意的是,如果我們的文件名中有通配符,如:“*”,那么可以用轉義字符“\”,如“\*”來表示真實的“*”字符,而不是任意長度的字符串。

            好吧,還是先來看幾個例子吧:

                clean:
            rm -f *.o
            
            上面這個例子我不不多說了,這是操作系統Shell所支持的通配符。這是在命令中的通配符。
                print: *.c
            lpr -p $?
            touch print
            
            上面這個例子說明了通配符也可以在我們的規則中,目標print依賴于所有的[.c]文件。其中的“$?”是一個自動化變量,我會在后面給你講述。
                objects = *.o
            
            上面這個例子,表示了,通符同樣可以用在變量中。并不是說[*.o]會展開,不!objects的值就是“*.o”。Makefile中的變量其實就是C/C++中的宏。如果你要讓通配符在變量中展開,也就是讓objects的值是所有[.o]的文件名的集合,那么,你可以這樣:
                objects := $(wildcard *.o)
            
            這種用法由關鍵字“wildcard”指出,關于Makefile的關鍵字,我們將在后面討論。

            3.4 文件搜尋

            在一些大的工程中,有大量的源文件,我們通常的做法是把這許多的源文件分類,并存放在不同的目錄中。所以,當make需要去找尋文件的依賴關系時,你可以在文件前加上路徑,但最好的方法是把一個路徑告訴make,讓make在自動去找。

            Makefile文件中的特殊變量“VPATH”就是完成這個功能的,如果沒有指明這個變量,make只會在當前的目錄中去找尋依賴文件和目標文件。如果定義了這個變量,那么,make就會在當當前目錄找不到的情況下,到所指定的目錄中去找尋文件了。

                VPATH = src:../headers
            
            上面的的定義指定兩個目錄,“src”和“../headers”,make會按照這個順序進行搜索。目錄由“冒號”分隔。(當然,當前目錄永遠是最高優先搜索的地方)

            另一個設置文件搜索路徑的方法是使用make的“vpath”關鍵字(注意,它是全小寫的),這不是變量,這是一個make的關鍵字,這和上面提到的那個VPATH變量很類似,但是它更為靈活。它可以指定不同的文件在不同的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種:

            1. vpath < pattern> < directories>
              為符合模式< pattern>的文件指定搜索目錄< directories>。
            2. vpath < pattern>
              清除符合模式< pattern>的文件的搜索目錄。
            3. vpath
              清除所有已被設置好了的文件搜索目錄。

            vapth使用方法中的< pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”結尾的文件。< pattern>指定了要搜索的文件集,而< directories>則指定了 的文件集的搜索的目錄。例如:

                vpath %.h ../headers
            
            該語句表示,要求make在“../headers”目錄下搜索所有以“.h”結尾的文件。(如果某文件在當前目錄沒有找到的話)

            我們可以連續地使用vpath語句,以指定不同搜索策略。如果連續的vpath語句中出現了相同的< pattern>,或是被重復了的< pattern>,那么,make會按照vpath語句的先后順序來執行搜索。如:

                vpath %.c foo
            vpath %   blish
            vpath %.c bar
            
            其表示“.c”結尾的文件,先在“foo”目錄,然后是“blish”,最后是“bar”目錄。
                vpath %.c foo:bar
            vpath %   blish
            
            而上面的語句則表示“.c”結尾的文件,先在“foo”目錄,然后是“bar”目錄,最后才是“blish”目錄。

            3.5 偽目標

            最早先的一個例子中,我們提到過一個“clean”的目標,這是一個“偽目標”,

                clean:
            rm *.o temp
            
            正像我們前面例子中的“clean”一樣,即然我們生成了許多文件編譯文件,我們也應該提供一個清除它們的“目標”以備完整地重編譯而用。 (以“make clean”來使用該目標)

            因為,我們并不生成“clean”這個文件。“偽目標”并不是一個文件,只是一個標簽,由于“偽目標”不是文件,所以make無法生成它的依賴關系和決定它是否要執行。我們只有通過顯示地指明這個“目標”才能讓其生效。當然,“偽目標”的取名不能和文件名重名,不然其就失去了“偽目標”的意義了。

            當然,為了避免和文件重名的這種情況,我們可以使用一個特殊的標記“.PHONY”來顯示地指明一個目標是“偽目標”,向make說明,不管是否有這個文件,這個目標就是“偽目標”。

                .PHONY : clean
            
            只要有這個聲明,不管是否有“clean”文件,要運行“clean”這個目標,只有“make clean”這樣。于是整個過程可以這樣寫:
                 .PHONY: clean
            clean:
            rm *.o temp
            
            偽目標一般沒有依賴的文件。但是,我們也可以為偽目標指定所依賴的文件。偽目標同樣可以作為“默認目標”,只要將其放在第一個。一個示例就是,如果你的Makefile需要一口氣生成若干個可執行文件,但你只想簡單地敲一個make完事,并且,所有的目標文件都寫在一個Makefile中,那么你可以使用“偽目標”這個特性:
                all : prog1 prog2 prog3
            .PHONY : all
            prog1 : prog1.o utils.o
            cc -o prog1 prog1.o utils.o
            prog2 : prog2.o
            cc -o prog2 prog2.o
            prog3 : prog3.o sort.o utils.o
            cc -o prog3 prog3.o sort.o utils.o
            
            我們知道,Makefile中的第一個目標會被作為其默認目標。我們聲明了一個“all”的偽目標,其依賴于其它三個目標。由于偽目標的特性是,總是被執行的,所以其依賴的那三個目標就總是不如“all”這個目標新。所以,其它三個目標的規則總是會被決議。也就達到了我們一口氣生成多個目標的目的。“.PHONY : all”聲明了“all”這個目標為“偽目標”。

            隨便提一句,從上面的例子我們可以看出,目標也可以成為依賴。所以,偽目標同樣也可成為依賴。看下面的例子:

                .PHONY: cleanall cleanobj cleandiff
            cleanall : cleanobj cleandiff
            rm program
            cleanobj :
            rm *.o
            cleandiff :
            rm *.diff
            
            “make clean”將清除所有要被清除的文件。“cleanobj”和“cleandiff”這兩個偽目標有點像“子程序”的意思。我們可以輸入“make cleanall”和“make cleanobj”和“make cleandiff”命令來達到清除不同種類文件的目的

            3.6 多目標

            Makefile的規則中的目標可以不止一個,其支持多目標,有可能我們的多個目標同時依賴于一個文件,并且其生成的命令大體類似。于是我們就能把其合并起來。當然,多個目標的生成規則的執行命令是同一個,這可能會可我們帶來麻煩,不過好在我們的可以使用一個自動化變量“$@”(關于自動化變量,將在后面講述),這個變量表示著目前規則中所有的目標的集合,這樣說可能很抽象,還是看一個例子吧。

                bigoutput littleoutput : text.g
            generate text.g -$(subst output,,$@) > $@
            
                上述規則等價于:
            
            bigoutput : text.g generate text.g -big > bigoutput littleoutput : text.g generate text.g -little > littleoutput
            其中,-$(subst output,,$@)中的“$”表示執行一個Makefile的函數,函數名為subst,后面的為參數。關于函數,將在后面講述。這里的這個函數是截取字符串的意思,“$@”表示目標的集合,就像一個數組,“$@”依次取出目標,并執于命令。

            3.7 靜態模式

            靜態模式可以更加容易地定義多目標的規則,可以讓我們的規則變得更加的有彈性和靈活。我們還是先來看一下語法:

            <targets ...>: <target-pattern>: <prereq-patterns ...>
               <commands>
            ...

            targets定義了一系列的目標文件,可以有通配符。是目標的一個集合。

            target-parrtern是指明了targets的模式,也就是的目標集模式。

            prereq-parrterns是目標的依賴模式,它對target-parrtern形成的模式再進行一次依賴目標的定義。

            這樣描述這三個東西,可能還是沒有說清楚,還是舉個例子來說明一下吧。如果我們的<target-parrtern>定義成“%.o”,意思是我們的集合中都是以“.o”結尾的,而如果我們的<prereq-parrterns>定義成“%.c”,意思是對<target-parrtern>所形成的目標集進行二次定義,其計算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]這個結尾),并為其加上[.c]這個結尾,形成的新集合。

            所以,我們的“目標模式”或是“依賴模式”中都應該有“%”這個字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”進行轉義,來標明真實的“%”字符。

            看一個例子:

                objects = foo.o bar.o
            all: $(objects)
            $(objects): %.o: %.c
            $(CC) -c $(CFLAGS) $< -o $@
            
            上面的例子中,指明了我們的目標從$object中獲取,“%.o”表明要所有以“.o”結尾的目標,也就是“foo.o bar.o”,也就是變量$object集合的模式,而依賴模式“%.c”則取模式“%.o”的“%”,也就是“foo bar”,并為其加下“.c”的后綴,于是,我們的依賴目標就是“foo.c bar.c”。而命令中的“$<”和“$@”則是自動化變量,“$<”表示所有的依賴目標集(也就是“foo.c bar.c”),“$@”表示目標集(也褪恰癴oo.o bar.o”)。于是,上面的規則展開后等價于下面的規則:
                foo.o : foo.c
            $(CC) -c $(CFLAGS) foo.c -o foo.o
            bar.o : bar.c
            $(CC) -c $(CFLAGS) bar.c -o bar.o
            
            試想,如果我們的“%.o”有幾百個,那種我們只要用這種很簡單的“靜態模式規則”就可以寫完一堆規則,實在是太有效率了。“靜態模式規則”的用法很靈活,如果用得好,那會一個很強大的功能。再看一個例子:
                files = foo.elc bar.o lose.o
            $(filter %.o,$(files)): %.o: %.c
            $(CC) -c $(CFLAGS) $< -o $@
            $(filter %.elc,$(files)): %.elc: %.el
            emacs -f batch-byte-compile $<
            

            $(filter %.o,$(files))表示調用Makefile的filter函數,過濾“$filter”集,只要其中模式為“%.o”的內容。其的它內容,我就不用多說了吧。這個例字展示了Makefile中更大的彈性。

            3.8 自動生成依賴性

            在Makefile中,我們的依賴關系可能會需要包含一系列的頭文件,比如,如果我們的main.c中有一句“#include "defs.h"”,那么我們的依賴關系應該是:

                main.o : main.c defs.h
            
            但是,如果是一個比較大型的工程,你必需清楚哪些C文件包含了哪些頭文件,并且,你在加入或刪除頭文件時,也需要小心地修改Makefile,這是一個很沒有維護性的工作。為了避免這種繁重而又容易出錯的事情,我們可以使用C/C++編譯的一個功能。大多數的C/C++編譯器都支持一個“-M”的選項,即自動找尋源文件中包含的頭文件,并生成一個依賴關系。例如,如果我們執行下面的命令:
                cc -M main.c
            
            其輸出是:
                main.o : main.c defs.h
            
            于是由編譯器自動生成的依賴關系,這樣一來,你就不必再手動書寫若干文件的依賴關系,而由編譯器自動生成了。需要提醒一句的是,如果你使用GNU的C/C++編譯器,你得用“-MM”參數,不然,“-M”參數會把一些標準庫的頭文件也包含進來。

            gcc -M main.c的輸出是:

                main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
            /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
            /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
            /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
            /usr/include/bits/sched.h /usr/include/libio.h \
            /usr/include/_G_config.h /usr/include/wchar.h \
            /usr/include/bits/wchar.h /usr/include/gconv.h \
            /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
            /usr/include/bits/stdio_lim.h
            
            gcc -MM main.c的輸出則是:
                main.o: main.c defs.h
            
            那么,編譯器的這個功能如何與我們的Makefile聯系在一起呢。因為這樣一來,我們的Makefile也要根據這些源文件重新生成,讓Makefile自已依賴于源文件?這個功能并不現實,不過我們可以有其它手段來迂回地實現這一功能。GNU組織建議把編譯器為每一個源文件的自動生成的依賴關系放到一個文件中,為每一個“name.c”的文件都生成一個“name.d”的Makefile文件,[.d]文件中就存放對應[.c]文件的依賴關系。

            于是,我們可以寫出[.c]文件和[.d]文件的依賴關系,并讓make自動更新或自成[.d]文件,并把其包含在我們的主Makefile中,這樣,我們就可以自動化地生成每個文件的依賴關系了。

            這里,我們給出了一個模式規則來產生[.d]文件:

                %.d: %.c
            @set -e; rm -f $@; \
            $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
            sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
            rm -f $@.$$$$
            

            這個規則的意思是,所有的[.d]文件依賴于[.c]文件,“rm -f $@”的意思是刪除所有的目標,也就是[.d]文件,第二行的意思是,為每個依賴文件“$<”,也就是[.c]文件生成依賴文件,“$@”表示模式“%.d”文件,如果有一個C文件是name.c,那么“%”就是“name”,“$$$$”意為一個隨機編號,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一個替換,關于sed命令的用法請參看相關的使用文檔。第四行就是刪除臨時文件。

            總而言之,這個模式要做的事就是在編譯器生成的依賴關系中加入[.d]文件的依賴,即把依賴關系:

                main.o : main.c defs.h
            
            轉成:
                main.o main.d : main.c defs.h
            
            于是,我們的[.d]文件也會自動更新了,并會自動生成了,當然,你還可以在這個[.d]文件中加入的不只是依賴關系,包括生成的命令也可一并加入,讓每個[.d]文件都包含一個完賴的規則。一旦我們完成這個工作,接下來,我們就要把這些自動生成的規則放進我們的主Makefile中。我們可以使用Makefile的“include”命令,來引入別的Makefile文件(前面講過),例如:
                sources = foo.c bar.c
            include $(sources:.c=.d)
            
            上述語句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一個替換,把變量$(sources)所有[.c]的字串都替換成[.d],關于這個“替換”的內容,在后面我會有更為詳細的講述。當然,你得注意次序,因為include是按次來載入文件,最先載入的[.d]文件中的目標會成為默認目標

            4 Makefile 書寫命令


            每條規則中的命令和操作系統Shell的命令行是一致的。make會一按順序一條一條的執行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是緊跟在依賴規則后面的分號后的。在命令行之間中的空格或是空行會被忽略,但是如果該空格或空行是以Tab鍵開頭的,那么make會認為其是一個空命令。

            我們在UNIX下可能會使用不同的Shell,但是make的命令默認是被“/bin/sh”——UNIX的標準Shell解釋執行的。除非你特別指定一個其它的Shell。Makefile中,“#”是注釋符,很像C/C++中的“//”,其后的本行字符都被注釋。

            4.1 顯示命令

            通常,make會把其要執行的命令行在命令執行前輸出到屏幕上。當我們用“@”字符在命令行前,那么,這個命令將不被make顯示出來,最具代表性的例子是,我們用這個功能來像屏幕顯示一些信息。如:

                @echo 正在編譯XXX模塊......
            
            當make執行時,會輸出“正在編譯XXX模塊......”字串,但不會輸出命令,如果沒有“@”,那么,make將輸出:
                echo 正在編譯XXX模塊......
            正在編譯XXX模塊......
            
            如果make執行時,帶入make參數“-n”或“--just-print”,那么其只是顯示命令,但不會執行命令,這個功能很有利于我們調試我們的Makefile,看看我們書寫的命令是執行起來是什么樣子的或是什么順序的。

            而make參數“-s”或“--slient”則是全面禁止命令的顯示。

            4.2 命令執行

            當依賴目標新于目標時,也就是當規則的目標需要被更新時,make會一條一條的執行其后的命令。需要注意的是,如果你要讓上一條命令的結果應用在下一條命令時,你應該使用分號分隔這兩條命令。比如你的第一條命令是cd命令,你希望第二條命令得在cd之后的基礎上運行,那么你就不能把這兩條命令寫在兩行上,而應該把這兩條命令寫在一行上,用分號分隔。如:

                示例一:
            exec:
            cd /home/hchen
            pwd
            示例二:
            exec:
            cd /home/hchen; pwd
            

            當我們執行“make exec”時,第一個例子中的cd沒有作用,pwd會打印出當前的Makefile目錄,而第二個例子中,cd就起作用了,pwd會打印出“/home/hchen”。

            make一般是使用環境變量SHELL中所定義的系統Shell來執行命令,默認情況下使用UNIX的標準Shell——/bin/sh來執行命令。但在MS-DOS下有點特殊,因為MS-DOS下沒有SHELL環境變量,當然你也可以指定。如果你指定了UNIX風格的目錄形式,首先,make會在SHELL所指定的路徑中找尋命令解釋器,如果找不到,其會在當前盤符中的當前目錄中尋找,如果再找不到,其會在PATH環境變量中所定義的所有路徑中尋找。MS-DOS中,如果你定義的命令解釋器沒有找到,其會給你的命令解釋器加上諸如“.exe”、“.com”、“.bat”、“.sh”等后綴。

            4.3 命令出錯

            每當命令運行完后,make會檢測每個命令的返回碼,如果命令返回成功,那么make會執行下一條命令,當規則中所有的命令成功返回后,這個規則就算是成功完成了。如果一個規則中的某個命令出錯了(命令退出碼非零),那么make就會終止執行當前規則,這將有可能終止所有規則的執行。

            有些時候,命令的出錯并不表示就是錯誤的。例如mkdir命令,我們一定需要建立一個目錄,如果目錄不存在,那么mkdir就成功執行,萬事大吉,如果目錄存在,那么就出錯了。我們之所以使用mkdir的意思就是一定要有這樣的一個目錄,于是我們就不希望mkdir出錯而終止規則的運行。

            為了做到這一點,忽略命令的出錯,我們可以在Makefile的命令行前加一個減號“-”(在Tab鍵之后),標記為不管命令出不出錯都認為是成功的。如:

               clean:
            -rm -f *.o
            
            還有一個全局的辦法是,給make加上“-i”或是“--ignore-errors”參數,那么,Makefile中所有命令都會忽略錯誤。而如果一個規則是以“.IGNORE”作為目標的,那么這個規則中的所有命令將會忽略錯誤。這些是不同級別的防止命令出錯的方法,你可以根據你的不同喜歡設置。

            還有一個要提一下的make的參數的是“-k”或是“--keep-going”,這個參數的意思是,如果某規則中的命令出錯了,那么就終目該規則的執行,但繼續執行其它規則。

            4.4 嵌套執行make

            在一些大的工程中,我們會把我們不同模塊或是不同功能的源文件放在不同的目錄中,我們可以在每個目錄中都書寫一個該目錄的Makefile,這有利于讓我們的Makefile變得更加地簡潔,而不至于把所有的東西全部寫在一個Makefile中,這樣會很難維護我們的Makefile,這個技術對于我們模塊編譯和分段編譯有著非常大的好處。

            例如,我們有一個子目錄叫subdir,這個目錄下有個Makefile文件,來指明了這個目錄下文件的編譯規則。那么我們總控的Makefile可以這樣書寫:

                subsystem:
            cd subdir && $(MAKE)
            其等價于:
            subsystem:
            $(MAKE) -C subdir
            
            定義$(MAKE)宏變量的意思是,也許我們的make需要一些參數,所以定義成一個變量比較利于維護。這兩個例子的意思都是先進入“subdir”目錄,然后執行make命令。

            我們把這個Makefile叫做“總控Makefile”,總控Makefile的變量可以傳遞到下級的Makefile中(如果你顯示的聲明),但是不會覆蓋下層的Makefile中所定義的變量,除非指定了“-e”參數。

            如果你要傳遞變量到下級Makefile中,那么你可以使用這樣的聲明:

            export <variable ...>
            如果你不想讓某些變量傳遞到下級Makefile中,那么你可以這樣聲明:
            unexport <variable ...>
            如:
            示例一:
            export variable = value
            其等價于:
            variable = value
            export variable
            其等價于:
            export variable := value
            其等價于:
            variable := value
            export variable
            示例二:
            export variable += value
            其等價于:
            variable += value
            export variable
            
            如果你要傳遞所有的變量,那么,只要一個export就行了。后面什么也不用跟,表示傳遞所有的變量。

            需要注意的是,有兩個變量,一個是SHELL,一個是MAKEFLAGS,這兩個變量不管你是否export,其總是要傳遞到下層Makefile中,特別是MAKEFILES變量,其中包含了make的參數信息,如果我們執行“總控Makefile”時有make參數或是在上層Makefile中定義了這個變量,那么MAKEFILES變量將會是這些參數,并會傳遞到下層Makefile中,這是一個系統級的環境變量。

            但是make命令中的有幾個參數并不往下傳遞,它們是“-C”,“-f”,“-h”“-o”和“-W”(有關Makefile參數的細節將在后面說明),如果你不想往下層傳遞參數,那么,你可以這樣來:

            subsystem:
            cd subdir && $(MAKE) MAKEFLAGS=
            
            如果你定義了環境變量MAKEFLAGS,那么你得確信其中的選項是大家都會用到的,如果其中有“-t”,“-n”,和“-q”參數,那么將會有讓你意想不到的結果,或許會讓你異常地恐慌。

            還有一個在“嵌套執行”中比較有用的參數,“-w”或是“--print-directory”會在make的過程中輸出一些信息,讓你看到目前的工作目錄。比如,如果我們的下級make目錄是“/home/hchen/gnu/make”,如果我們使用“make -w”來執行,那么當進入該目錄時,我們會看到:

            make: Entering directory `/home/hchen/gnu/make'.
            
            而在完成下層make后離開目錄時,我們會看到:
            make: Leaving directory `/home/hchen/gnu/make'
            
            當你使用“-C”參數來指定make下層Makefile時,“-w”會被自動打開的。如果參數中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”總是失效的。

            4.5 定義命令包

            如果Makefile中出現一些相同命令序列,那么我們可以為這些相同的命令序列定義一個變量。定義這種命令序列的語法以“define”開始,以“endef”結束,如:

                define run-yacc
            yacc $(firstword $^)
            mv y.tab.c $@
            endef
            
            這里,“run-yacc”是這個命令包的名字,其不要和Makefile中的變量重名。在“define”和“endef”中的兩行就是命令序列。這個命令包中的第一個命令是運行Yacc程序,因為Yacc程序總是生成“y.tab.c”的文件,所以第二行的命令就是把這個文件改改名字。還是把這個命令包放到一個示例中來看看吧。
                foo.c : foo.y
            $(run-yacc)
            
            我們可以看見,要使用這個命令包,我們就好像使用變量一樣。在這個命令包的使用中,命令包“run-yacc”中的“$^”就是“foo.y”,“$@”就是“foo.c”(有關這種以“$”開頭的特殊變量,我們會在后面介紹),make在執行命令包時,命令包中的每個命令會被依次獨立執行。
            posted @ 2009-08-03 14:29 chaosuper 閱讀(132) | 評論 (0)編輯 收藏

            僅列出標題
            共12頁: First 4 5 6 7 8 9 10 11 12 
            一级做a爰片久久毛片看看| 久久久久国产亚洲AV麻豆| 久久精品国产精品青草| 久久精品无码一区二区app| 7777精品伊人久久久大香线蕉| 久久青青草原亚洲av无码app| 精品久久久久一区二区三区 | 性高朝久久久久久久久久| 色播久久人人爽人人爽人人片AV| 久久精品国产网红主播| 久久人搡人人玩人妻精品首页| 99精品国产综合久久久久五月天| 精品一区二区久久| 亚洲午夜无码久久久久| 久久99久久无码毛片一区二区| 久久亚洲sm情趣捆绑调教| 国产成人99久久亚洲综合精品| 亚洲综合日韩久久成人AV| 国产99久久久国产精免费| 久久精品蜜芽亚洲国产AV| 性做久久久久久久久浪潮| 日本免费一区二区久久人人澡 | 国产综合久久久久| 久久久久亚洲AV成人网人人网站 | 国产视频久久| 久久国产精品久久| 人妻精品久久久久中文字幕一冢本| 久久久久亚洲AV无码去区首| 久久精品国产亚洲综合色| 中文字幕久久波多野结衣av| 开心久久婷婷综合中文字幕| 精品久久久久久无码中文字幕| 蜜桃麻豆www久久| 久久国产精品国产自线拍免费| .精品久久久麻豆国产精品| 婷婷久久久亚洲欧洲日产国码AV | 久久免费视频观看| 久久亚洲国产午夜精品理论片| a级成人毛片久久| 久久久精品免费国产四虎| 91麻精品国产91久久久久|