(1) 首先用vi編輯器創建一個c程序文件(以.c結尾) 如: vi hello.c #include <stdio.h> int main() { printf(“Hello World!\n”); return 0; } 創建好hello.c文件後,保存退出,接下來就是進行編譯程序。 gcc hello.c 此時,用ls -al命令查看當前目錄下的文件,可以發現,多出了一個a.out文件,注意該文件權限的最後一列是x,即表示該文件是可執行文件,讓我們執行文件看看會有什麼結果! ./a.out 命令窗口出現了“Hello World”。但現在有個問題,如果我們不想生成的可執行使用默認名稱,那要怎麼辦呢?這裡可以使用-o這一選項決定生成文件的名稱,我們就給執行文件命名為run吧。 gcc hello.c -o run 這次,在用ls -al查看當前目錄,發現出現了run可執行文件,再次運行該文件: ./run 結果和./a.out是一樣的。 (2) 有了上面的基礎後,我們繼續接下來的教程。這次我們修改上面的.c文件: vi hello.c 使之內容如下,實現簡單的相加功能: #include <stdio.h> int main() { double x = 1.3, y = 2.4; printf(“sum:%d”, x + y); return 0; } 仔細的觀察,會發現,輸出格式上問題,我們要輸出的是一個double型的數據,但輸出格式是按照整形輸出,雖說這不影響程序的運行,但卻可能是一個隱藏的錯誤。讓我們按照上一節的方法編譯文件: gcc hello.c -o run 結果,編譯器沒有提示任何的警告。這對於一些對代碼規范要求比較嚴格的人來說,是一個嚴重的漏洞。那麼,要怎樣才可以看到提示警告呢。我們可以使用-Wall選項來顯示警告。 gcc -Wall hello.c -o run 這次編譯器就顯示警告消息了。接下來,我們的大部分例子都是使用-Wall這一選項,我會在之後的內容中對-Wall的使用進一步詳解。 (3) 在編程中,我們經常會調用其他文件的函數,因為不可能把所有的函數都在一個文件上實現,這樣會讓代碼很難管理。那麼如果在linux上,存在多文件關系又要怎樣編譯呢?下面我舉一個簡單例子: 首先是,add.c文件 vi add.c int add(int x, int y){ return x + y; } 接著就是包含該函數聲明的頭文件math.h vi math.h int add(int x, int y); 然後是調用add這一函數的main.c文件: vi main.c #include <stdio.h> #include”math.h” int main() { int sum = 0; sum = add(4, 3); printf(“sum:%d\n”, sum); return 0; } 這裡,我簡單地講講gcc編譯器的工作流程,首先是對源程序進行預處理,然後是生成目標文件,最後是將目標文件鏈接在一起形成可執行文件。大概的流程是這樣,想要知道更具體的細節,大家可以自己到網上去找相關的資料,這裡我就不展開講了。接下來就是生成目標文件: gcc -Wall -c add.c gcc -Wall -c main.c 使用ls -al查看當前目錄,可以見到新增了兩個目標文件,分別是add.o 和main.o。這裡,我們使用-c表示只生成目標文件,不生成可執行文件。所以編譯其並沒有報錯。接下來就是將兩個目標文件鏈接在一起形成最終的可執行文件。 gcc -Wall main.o add.o -o result 查看當前目錄,這是就出現了result這一可執行文件,讓我們運行看看是否程序是否正確。 ./result 沒錯,輸出的結果就是7。這是大家就會疑問了,難道就沒有簡單一點的方法嗎,其實上面的步驟可以用一句命令來代替: gcc -Wall main.c add.c -o result 我之所以將上面一種比較麻煩的方法是想讓大家了解一下gcc的編譯流程,因為到了後面我們還會繼續用到這種方法。 這時,一些細心的朋友可能會發現math.h文件沒有在命令行列,那是因為#include”math.h”中編譯器已經將math.h中的內容包含到main.c中了。 (4)上面的程序很簡單,這次我們創建一個更復雜點的程序,讓我們把math.h頭文件下的函數補充完善 , 分別追加minus.c divide.c以及multiply.c其實就是把我們常用的加減乘除功能全部實現。 vi minus.c int minus(int x, int y){ return x – y; } vi divide.c int divide(int x, int y){ return x / y; } vi multiply.c int multiply(int x, int y){ return x * y; } 然後是將各個函數的聲明添加之math.h文件中, vi math.h int add(int x, int y); int minus(int x, int y); int divide(int x, int y); int multiply(int x, int y); 同時,我們修改一下main.c,讓其調用divide函數: vi main.c #include <stdio.h> #include”math.h” int main() { int sum = 0; sum = add(4, 3); printf(“sum:%d\n”, sum); int result = 0; result = divide(4,2); printf(“result:%d”, result); return 0; } 這次由於main.c中,我們只調用了add和divide函數,則編譯命令如下: gcc -Wall main.c add.c divide.c -o result 查看當前目錄,result已經生成,運行一下程序: ./result 結果正常。我們繼續把剩下的兩個minus.c和multiply.c生成目標文件,供以後使用。這時用ls -al查看當前目錄,我們就有add.o、minus.o、divide.o、multiply.o和main.o四個目標文件。 讓我們回過頭來看看前面的代碼,細心的朋友會發現,我們的divide函數有個漏洞,如果分母y為0,那麼程序就會出錯,我們再次編輯divide.c文件: vi divide.c 添加一個判斷語句即可。 #include <stdio.h> //由於我們調用了printf系統自帶的函數 int divide(int x, int y){ if(y == 0){ printf(“y can't be zero\n”); return 0; } return x / y; } 然後,我們修改main.c,測試一下分母為0時是否可以正常運行。 vi main.c #include <stdio.h> #include”math.h” int main() { int sum = 0; sum = add(4, 3); printf(“sum:%d\n”, sum); int result = 0; result = divide(4,0); printf(“result:%d”, result); return 0; } 這時我們就有疑問了,當前目錄下的result可執行程序是修改之前的編譯生成的可執行文件,那麼修改之後就得重新編譯一次。這裡,由於我們使用的是多文件的方法,那麼只需重新編譯那些改動過的文件 ,這裡我們需要重新編譯divide.c和main.c文件,前面一個大家自然理解,至於後面一個呢,那是因為main.c函數調用了divide函數,那要想使用更新後的函數,自然就的重新編譯編譯一邊才可以享受更新服務了,這其實就是linux上常說的文件依賴。一個文件A依賴文件B,當文件B發生改動時,不僅文件B要重新編譯,文件A也要重新編譯一遍。這裡由於其他文件沒有發生改動,就無需重新編譯一遍, 這樣就大大的提高了編譯的效率,這也是我們使用多文件管理的原因之一。 好了,說了那麼多,讓我們直接運行下代碼看看結果先吧! gcc -Wall main.o add.o divide.o -o result 結果顯示說,分母y不能為0,看來程序正常運行了。 這次教程就到此為止。 (5)這一次教程,我們來說說關於linux下的庫。庫說白了其實就是一個個目標文件的集合。linux上一共有兩種庫文件類型,一種是以.a格式的靜態鏈接庫,另一種是以.so格式的動態鏈接庫。下面我們來講講靜態鏈接庫。上一次的教程,我們生成了四個目標文件,分別add.o、minus.o、divide.o、multiply.o。這次,我們使用ar程序把這四個目標文件打包成一個靜態鏈接庫,以後要調用多個函數時,就不必一個一個的鏈接對應的目標文件。廢話不多說,我們直接用代碼來解釋: ar的使用格式: ar cr libName.a file1.o file2.o file3.o …... 注釋:libName.a中前綴lib和後綴.a固定,Name是靜態鏈接庫的名稱。 ar cr libtest.a add.o、minus.o、divide.o、multiply.o 執行命令後,我們查看當前目錄下的文件,就多出了一個libtest.a文件。接著,我們就直接使用libtest.a來重新編譯源程序: gcc -Wall main.c libtest.a -o result2 程序結果和之前的一樣。我們也可以使用下面這種方法: gcc -Wall main.c -L -ltest. -o result2 注意,上面的”-L.”不能缺少,因為,使用”-l“,編譯器查找的是系統默認的庫文件地址,而不是當前目錄,故需要使用-L來說明庫文件地址,由於我們的libtest.a在當前目錄下,故直接使用“.”表示當前目錄。以後,我們向別人提供第三方函數庫時,如果不想讓別人看到源代碼,那麼就可以只提供.a靜態鏈接庫和包含所有函數聲明的頭文件即可。 (6)在之前所有的教程裡,我們都是把生成的目標文件、靜態鏈接庫以及頭文件放在同一個文件夾下,這樣不僅顯得很雜亂,也不便管理源文件,一旦程序的文件數目龐大後,問題就愈加突出。下面我們就對程序文件整理一下,一般來說,頭文件放在include 文件夾下,靜態鏈接庫lib文件夾下。下面先創建兩個文件夾,並把相應的文件移動到對應文件夾下。 mkdir include mkdir lib mv libtest.a lib mv math.h include 由於我們已經將四個目標文件(add.o、minus.o、divide.o、multiply.o)打包成靜態鏈接哭libtest.a,那麼就可以刪除這四個文件: rm add.o minus.o divide.o multiply.o 這是當前目錄就只剩下main.c、add.c、minus.c、divide.c、multiply.c這五個源文件。 首先,我們用比較麻煩的方法一步步地來編譯源文件,首先是生成目標文件,由於add.c、minus.c、divide.c、multiply.c這四個源代碼的目標文件的已經生成並被打包到靜態鏈接庫中,故我們只需生成main.c的目標文件即可: gcc -Wall -Iinclude -c main.c 這裡-Iinclude中-I後面接的是main.c中所引用頭文件(math.h)的地址,這裡使用的是相對地址,其實也可以使用絕對地址,當由於不同的linux系統,他們的文件位置可能有寫差異,這就造成我們程序的移植性很差,所以還是建議大家使用相對地址。 接下就鏈接目標文件即可。 gcc -Wall main.o -Llib -ltest -o result3 這裡-Llib是-L後面接的是靜態鏈接庫的地址,由於我們的libtest.a在lib中,故接lib