中山野鬼 linux 下 C 編程和make的方法 (2、基礎準備:編譯與鏈接&GCC)

前期最基本的知識:
    關於目標,咱們要寫個程序。能夠有不少用途。但絕大多數狀況下,是爲了運行。咱們運行的目的,不是爲了RUN。估計沒有哪一個人會如此寫個函數
1 while (1){
2        i++;
3     }
        沒錯,什麼也不幹。就是爲了耗電,和證實本身的電腦能夠用。特意加上i++的目的,仍是要告訴編譯器,「HI,別把我這段代碼給省略了」.
    所以,正常的思惟邏輯應該是這樣的因果關係:
    由於我要實現的結果,用計算機更方便,因此我要讓計算機來運行使得出現結果,爲了讓計算機運行,而現有的計算機軟件沒法實現,或實現很差,因此我須要親自 教育計算機怎麼實現。沒錯,你就是手持鞭子的人,而計算機就是那個戴着眼罩的驢。但重點不是驢轉了多少圈,重點是,有個磨出了你想要的東西。從這個地方, 須要引伸個話題。不是隨便怎麼抽鞭子就能夠有結果的。
    所以,抽鞭子是有方法和規則的,是受約束的,一鞭子下去把驢抽死了,確定不是你想要看到的。同時,鞭子在手,不表明事情作完。
    對比,程序編寫,那麼程序編寫完了,準確說是錄入完了,事情並無作完。真正要出結果,還有兩個步驟「編譯」,「執行」。「執行」此處不展開。展開一下「編譯」。
    這裏說的「編譯」是個廣義的詞。包含了「編譯和鏈接」。其實你要記住既有編譯,又有連接,只要記住一句話,以下:
    C語言的國際標準(好大的招牌,沒辦法,招牌大有好處,防止別人砸場子,特別是基礎知識方面),要求,編譯的對象是文件,也就是說,不管你有多少個C文件組成的工程,始終是每一個文件獨立編譯的。那麼就是由於這樣,因此你的總要有個鏈接的動做吧。
(注意此處是C語言,一些語言的編譯的內涵和此處的不一致,但C語言的編譯,和整個計算機領域的編譯是比較對應的,這也是學C的好處)。
    這裏說說,爲何要編譯,爲何要鏈接(其實增強記憶的弱智版說法上面已經給了)。

    爲何要編譯:
    由於機器執行是機器語言,如同那頭驢,你和它交流「之乎者也」它是不理睬的。鞭子的動做傳達了你的信息,它就很聽話。這就是爲何要編譯。根本緣由就是你 的寫的程序,機器看不懂。除非你用機器碼描述,二進制的記錄在磁盤文件裏,這是能夠的。不然你始終要編譯。包括彙編,別覺得以下的文本代碼機器能理解
1 add a0,#1
2 mov a1,a0
    彙編的文本,只是儘量保證每條文本行,能夠對應有個機器碼存在。相對其餘高級語言,他是和機器語言確實是一一對應的(不少狀況下,但不絕對),但仍然不是機器的語言。
    由此總結一下,你要讓機器實現你的目的,須要保證如下幾個事實存在。
    一、機器開着。
    二、給機器看得懂的東西。
    三、讓機器根據你的要求,執行。
    爲了讓2能更好的實現。你一般須要如下幾個步驟。
    一、根據你的目標,設想一下機器執行的方法,並給出計劃圖。你打算這樣或那樣的讓機器去工做。並造成你的計劃圖。這就是編程。
    二、翻譯一下。包括鏈接。不然C翻譯完了,就是一一對應的OBJ文件,你仍然沒法實現第3步。
    那麼如下是一個不少新手容易錯誤理解的概念。
    編程必定要用特定的軟件實現。例如,這個語言,必定要用VC2008,那個語言必定要用eclipse。
    什麼是編程。弱智解釋:
    編程就是個計劃,參考一下工具的使用規範,你編造出一個流程。不要認爲編程是基於什麼工具下的方式,你只是在藉助工具,沒有鞭子,就扯褲腰帶啊,和你抽驢 有問題嗎?驢會由於你用褲腰帶而告你虐待動物,或對你很無奈的說一句「HI,雖然很疼,但畢竟不是鞭子,我不會繼續前進」。
    固然爲了有效實現,當你確認好具體工具時,須要依賴工具的規範執行,例如你須要使用C語言的編程的規範,但這個和具體編程軟件又沒有關係了。如上面說的, 讓機器執行的第二個步驟的展開,是,你造成計劃,並翻譯成機器的語言,你基本的工具是C的編譯器,而不是編寫你計劃的文本工具。

    這裏簡單展開一下編譯的工做組成。個人意見是三個階段:
    一、預編譯。
    二、前端編譯。至關於書本里的「分析部分」
    三、後端編譯。至關於書本里的「綜合部分」
    先分割2,3,前段編譯是作些和具體硬件沒有關係的事情。例如,你是要抽驢,或用個二衝程發動機驅動的剪草機,你的計劃在實現過程當中確定有些特定對象的東 西。這些就是後端折騰的。可是你的整個計劃也有和特定對象沒有關係的,這就是前端。若是一個C代碼,能夠在不一樣的機器上運行(固然須要從新編譯),那麼你 的C代碼的邏輯(和特定機器無關)的處理,則是前端。落實到具體步驟的執行,則是後端。爲何C容易移植以及普遍的被硬件平臺支持,其中一個緣由是:C語 言的編譯器被普遍使用和深刻研究,良好的分割了前端和後端,而硬件廠家,重點放在後端實現上。(因此當JAVA說本身是「與系統無關,跨平臺」的語言,C 的前端編譯器會很無恥的輕鬆飄過,由於找罵的事情一般是後端部分,而若是被JAVA忽悠而迷信「與系統無關,跨平臺」這句話的程序員,同樣會和後端編譯那 樣,被罵)
    那麼爲何要獨立提出「預編譯」由於,C的國際標準中,對於預處理,有明確的保留字,例如#include ,#define之類的。同時,預處理的工做目的不是在編譯,在於方便編譯。是爲了後續實際編譯工做對文本作的從新組織。甚至你徹底能夠很「可恥」的獨立 替換掉預編譯部分,將C語言構形成另外一種語言,而仍然使用C語言的編譯器。例如你能夠增長一個關鍵詞「class",當出現「class"的描述,你本身 來檢測語法,而且,將"class"的一些擴展用法,替換成C語言的實現方式,最終再調用C語言的編譯器,這些能夠獨立區分出來的事情,都屬於預編譯部 分,其實你雖然提出了一個新語言,但並非真正的獨立語言,你仍然是基於C的。
    不過須要很是明確的是,C++不是上述的狀況。C++有本身的國際標準和本身的編譯系統。但從理論上說。你徹底能夠根據C++的國際標準,作一套預編譯系統,而後調用C的編譯器一樣實現。至於效率問題,則看你的預編譯手段是否高明瞭。

    這裏引伸一個新手容易犯的理解錯誤。
    頭文件是C程序的一部分。
    標準只是規定了,根據文件來編譯,同時我暫時沒有查到必定要用C後綴的名字的約束要求。固然.c和.h同樣,已經嵌入到編譯軟件裏,成爲一些默認的格式規 則。但這個不能來講明,.h(默認爲頭文件),屬於編譯的對象。除非你的.h的書寫也是個標準的C語言的代碼文件。
    頭文件,只有在#include時,會被預編譯,插入到對應.c文件的對應位置,最終,編譯器不會考慮對頭文件進行處理。之因此處理它,是由於它的內容, 實際被嵌入到C文件中,此時實際仍是對C文件內容進行的編譯工做。而不是對.h文件。但這裏須要說明一點,如今有一種「預編譯」技術。試想,若是不少C文 件都包含相同的頭文件,爲何咱們不能把這些頭文件中,很是獨立,或者落到每一個C文件內都徹底相同的邏輯組織,先一次折騰好呢?但這個只是屬於優化的範 疇。和頭文件不屬於編譯對象不是一個範疇。
    廢話了這麼多,是但願你們能理解程序從書寫到執行中的必要經歷過程。這樣能夠明確以下的命令。gcc的 。
    gcc xx.c
    gcc xx.c -o xx
    gcc -c XX.c   
    gcc -c xx.c xx.o
    gcc -c xx.c -o xx.o
    gcc xx.o -o xx
    gcc根據文件後綴名,潛規則了一些操做。沒辦法,GCC仍是很不錯的,更重要是,其餘編譯器也有潛規則。因此你被潛規則是逃離不了的事實,所以,保持良好的心態,「與其默默的忍受被潛規則,不如默默的享受被潛規則」。
    下面給出上面的潛規則。
    gcc xx.c 。很簡單,幹活,幹什麼活,你沒告訴他,他就一干到底,把程序變成執行文件。由於你告訴gcc的信息不多。只有一個.c算是潛規則,此時,gcc必然會作 不少。包括了預編譯,編譯,鏈接。並且因爲你沒有告訴生成的程序(可執行的)是什麼名字,所以默認的使用a.out。由於linux下,沒有規定可執行程 序用什麼後綴名。所以,gcc給你的最終輸出,起名叫作 xx.exe,會很弱智,也很微軟。但GCC也只會給你最終的文件,中間的生成文件均不會給你。會被丟棄掉。好比obj文件。
    gcc xx.c -o xx,這裏比上面一個命令多了後面的XX,實際是你告訴了更多gcc信息。意思也就變成了,我要我本身的程序名稱。此時,就不會出現a.out這個不知到哪來的文件了。最終生成的執行文件,就是 xx。

    gcc -c xx.c  ,-c表示如今的動做是編譯,compile的簡寫,很容易記得。你記得,-c就是說,我只編譯。因爲gcc對文件後綴名是敏感的,那麼這個時候在gcc 的世界裏,認爲對象文件,也就是編譯的生成文件,用.o表示,此時,就會自動生成一個名爲xx.o的文件。
    gcc -c xx.c -o xx.o這個就容易理解了。-o實際的含義是強制輸出成你想要的名字而已。好比你能夠
    gcc -c xx.c -o xxx.o,也能夠。
    gcc xx.o -o xx,前面說過了。gcc有潛規則,若是你是.o文件則是obj,也就是目標文件,那麼此時gcc會知道。哦,你讓我操做目標文件。那我就連接吧。由此造成執行文件。後面的xx也就是執行文件。
    所以能夠準確說,以下幾個動做是和
    gcc xx.c等同的。
        $gcc -c xx.c -o xx.o
        $gcc xx.o -o a.out
        $rm xx.o
    這個時候,確定有人問,大爺鍵盤不累嗎?一句話的事情爲何要分幾個命令
去執行。這裏咱們關注兩個事情。
    一、屢次執行,你能夠不使用a.out,而改爲你想要的名字,夠爽吧。但這樣,咱們徹底能夠gcc xx.c -o XX實現。
    二、若是你有100個文件組成的程序。先要編譯100個文件造成.o,再對100個.o文件進行鏈接造成執行程序。但若是你只改動了1個C文件顯然你須要 從新編譯,這個時候,磁盤確定會罵你。「至於嗎?刪掉99個文件,再從新折騰一邊,當我磁盤是神燈啊。有事沒事都擦一擦」。沒錯,分開寫的目的,對於一個 C文件而言沒有意義,但那是對於實際大多數工程(包含多個C文件時)就有意義了。能夠不要從新對全部文件進行在編譯。記得編譯和連接的不一樣,你就能理解 了。

    說了這麼多,主要是讓新手可以實實在在的使用gcc命令,不用過gcc命令,你就不知道make的好處。
    下面貼一下兩個文件和全套操做步驟建議新手,重頭到位執行一邊。
前期準備,
確認 GCC存在。ubuntu 下,能夠
$gcc -version
大多數linux自帶make。
確認你具有一個目錄下的讀寫操做權限。
 
1 $mkdir learn_make
2  $cd learn_make
3  
4  $gedit learn_make.c   //注意我用的是scribes learn_make.c ,你能夠在 ubuntu下sudo apt-get install scribes,爲何用scribes參見下面。

以下內容前端

1 #include <stdio.h>
2 #include "learn_make.h"
3   
4 int main(int argc,char *argv[]){//你先別問main的函數有幾種寫法,爲何這麼寫
5     printf("%s\n",TEST_GCC_CMD);//一個打印對命令"%s\n"是一種格式化的規則,TEST_GCC_CMD在頭文件中定義了。
6     return 0;//main 的正常返回爲0。main對返回值對於程序調用程序時,仍是頗有意義的,養成別隨便返回的好習慣
7 }

//編輯完畢後保存關閉,退出     linux

1 $gedit learn_make.h

以下:程序員

01 #ifndef _LEARN_MAKE_H_ //我本身參考其餘軟件寫法的習慣,對於頭文件的防重複編譯的定義,
02 //採用文件命全大寫,先後加_的方式實現
03 #define _LEARN_MAKE_H_
04 /*
05     若是你想知道爲何要
06     #ifndef XXXX
07     #define XXXX
08     你嘗試這個頭文件裏,加上#include "learn_make.h"並嘗試本身手工,
09 將#include對應的文件內容COPY到這個位置,如同預處理那樣作的,
10 你再簡單的分析一下,是否會無限循環的加載文件內容,就能夠理解了。
11 */
12 #define TEST_GCC_CMD "test gcc cmd"
13 #endif //_LEARN_MAKE_H_
14 //這裏的寫法只是讓你知道這個#endif對應那個#if #ifdef #ifndef等等存在做用域的預處理工做

//編輯完畢後,保存關閉,退出    編程

01     $gcc learn_make.c
02     $ls
03     $./a.out
04     $rm a.out
05     $gcc learn_make.c -o haha
06     $ls
07     $./haha
08     $rm haha
09     $gcc -c learn_make.c
10     $ls
11     $gcc learn_make.o -o haha_by_o
12     $ls
13     $./haha_by_o
14     $rm learn_make.o
15     $rm haha_by_o
16     $gcc -c learn_make.c -o heihei
17     $ls
18     $gcc heihei -o haha_by_heihei //你能夠嘗試gcc heihei.o -o haha_by_heihei ,書上永遠教對的,我喜歡說錯的事情。
19     $ls
20     $./haha_by_heihei
       對於新手。我能夠這麼說,若是你不實際把上面操做都敲一邊,你就沒有學習make的必要了。由於鼓勵你學習make的一個動力就是上面的這些繁瑣的事情,能夠腳本化的簡單實現。
相關文章
相關標籤/搜索