## 21.2 使用傳統程序語言進行編譯的簡單范例
經過上面的介紹之后,你應該比較清楚的知道源代碼、編譯器、函數庫與可執行文件之間的相關性了。 不過,詳細的流程可能還是不很清楚,所以,在這里我們以一個簡單的程序范例來說明整個編譯的過程喔!趕緊進入 Linux 系統,實地的操作一下下面的范例呢!
### 21.2.1 單一程序:印出 Hello World
我們以 Linux 上面最常見的 C 語言來撰寫第一支程序!第一支程序最常作的就是..... 在屏幕上面印出“Hello World!”的字樣~當然, 這里我們是以簡單的 C 語言來撰寫,如果你對于 C 有興趣的話,那么請自行購買相關的書籍喔! ^_^ 好了,不啰唆,立刻編輯第一支程序吧!

**Tips** 請先確認你的 Linux 系統里面已經安裝了 gcc 了喔!如果尚未安裝 gcc 的話,請先參考下一節的 RPM 安裝法,先安裝好 gcc 之后,再回來閱讀本章。 如果你已經有網絡了,那么直接使用“ yum groupinstall "Development Tools" ” 預先安裝好所需的所有軟件即可。 rpm 與 yum 均會在下一章介紹。
* 編輯程序碼,亦即源代碼
```
[root@study ~]# vim hello.c <==用 C 語言寫的程序擴展名建議用 .c
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
}
```
上面是用 C 語言的語法寫成的一個程序文件。第一行的那個“ # ”并不是注解喔!如果你擔心輸入錯誤, 請到下面的鏈接下載這個文件:
* [http://linux.vbird.org/linux_basic/0520source/hello.c](http://linux.vbird.org/linux_basic/0520source/hello.c)
* 開始編譯與測試執行
```
[root@study ~]# gcc hello.c
[root@study ~]# ll hello.c a.out
-rwxr-xr-x. 1 root root 8503 Sep 4 11:33 a.out <==此時會產生這個文件名
-rw-r--r--. 1 root root 71 Sep 4 11:32 hello.c
[root@study ~]# ./a.out
Hello World <==呵呵!成果出現了!
```
在默認的狀態下,如果我們直接以 gcc 編譯源代碼,并且沒有加上任何參數,則可執行文件的文件名會被自動設置為 a.out 這個文件名稱! 所以你就能夠直接執行 ./a.out 這個可執行文件啦!上面的例子很簡單吧!那個 hello.c 就是源代碼,而 gcc 就是編譯器,至于 a.out 就是編譯成功的可執行 binary program 啰! 咦!那如果我想要產生目標文件 (object file) 來進行其他的動作,而且可執行文件的文件名也不要用默認的 a.out ,那該如何是好?其實你可以將上面的第 2 個步驟改成這樣:
```
[root@study ~]# gcc -c hello.c
[root@study ~]# ll hello*
-rw-r--r--. 1 root root 71 Sep 4 11:32 hello.c
-rw-r--r--. 1 root root 1496 Sep 4 11:34 hello.o <==就是被產生的目標文件
[root@study ~]# gcc -o hello hello.o
[root@study ~]# ll hello*
-rwxr-xr-x. 1 root root 8503 Sep 4 11:35 hello <==這就是可可執行文件! -o 的結果
-rw-r--r--. 1 root root 71 Sep 4 11:32 hello.c
-rw-r--r--. 1 root root 1496 Sep 4 11:34 hello.o
[root@study ~]# ./hello
Hello World
```
這個步驟主要是利用 hello.o 這個目標文件制作出一個名為 hello 的可執行文件,詳細的 gcc 語法我們會在后續章節中繼續介紹!通過這個動作后,我們可以得到 hello 及 hello.o 兩個文件, 真正可以執行的是 hello 這個 binary program 喔! 或許你會覺得,咦!只要一個動作作出 a.out 就好了,干嘛還要先制作目標文件再做成可執行文件呢? 呵呵!通過下個范例,你就可以知道為什么啦!
### 21.2.2 主、副程序鏈接:副程序的編譯
如果我們在一個主程序里面又調用了另一個副程序呢?這是很常見的一個程序寫法, 因為可以簡化整個程序的易讀性!在下面的例子當中,我們以 thanks.c 這個主程序去調用 thanks_2.c 這個副程序,寫法很簡單:
* 撰寫所需要的主、副程序
```
# 1\. 編輯主程序:
[root@study ~]# vim thanks.c
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
thanks_2();
}
# 上面的 thanks_2(); 那一行就是調用副程序啦!
[root@study ~]# vim thanks_2.c
#include <stdio.h>
void thanks_2(void)
{
printf("Thank you!\n");
}
```
上面這兩個文件你可以到下面下載:
* [http://linux.vbird.org/linux_basic/0520source/thanks.c](http://linux.vbird.org/linux_basic/0520source/thanks.c)
* [http://linux.vbird.org/linux_basic/0520source/thanks_2.c](http://linux.vbird.org/linux_basic/0520source/thanks_2.c)
* 進行程序的編譯與鏈接 (Link)
```
# 2\. 開始將源代碼編譯成為可執行的 binary file :
[root@study ~]# gcc -c thanks.c thanks_2.c
[root@study ~]# ll thanks*
-rw-r--r--. 1 root root 75 Sep 4 11:43 thanks_2.c
-rw-r--r--. 1 root root 1496 Sep 4 11:43 thanks_2.o <==編譯產生的!
-rw-r--r--. 1 root root 91 Sep 4 11:42 thanks.c
-rw-r--r--. 1 root root 1560 Sep 4 11:43 thanks.o <==編譯產生的!
[root@study ~]# gcc -o thanks thanks.o thanks_2.o
[root@study ~]# ll thanks*
-rwxr-xr-x. 1 root root 8572 Sep 4 11:44 thanks <==最終結果會產生這玩意兒
# 3\. 執行一下這個文件:
[root@study ~]# ./thanks
Hello World
Thank you!
```
知道為什么要制作出目標文件了嗎?由于我們的源代碼文件有時并非僅只有一個文件,所以我們無法直接進行編譯。 這個時候就需要先產生目標文件,然后再以鏈接制作成為 binary 可可執行文件。另外,如果有一天,你更新了 thanks_2.c 這個文件的內容,則你只要重新編譯 thanks_2.c 來產生新的 thanks_2.o ,然后再以鏈接制作出新的 binary 可可執行文件即可!而不必重新編譯其他沒有更動過的源代碼文件。 這對于軟件開發者來說,是一個很重要的功能,因為有時候要將偌大的源代碼全部編譯完成,會花很長的一段時間呢!
此外,如果你想要讓程序在執行的時候具有比較好的性能,或者是其他的除錯功能時, 可以在編譯的過程里面加入適當的參數,例如下面的例子:
```
[root@study ~]# gcc -O -c thanks.c thanks_2.c <== -O 為產生最優化的參數
[root@study ~]# gcc -Wall -c thanks.c thanks_2.c
thanks.c: In function ‘main’:
thanks.c:5:9: warning: implicit declaration of function ‘thanks_2’ [-Wimplicit-function-declaration]
thanks_2();
^
thanks.c:6:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
# -Wall 為產生更詳細的編譯過程信息。上面的訊息為警告訊息 (warning) 所以不用理會也沒有關系!
```
至于更多的 gcc 額外參數功能,就得要 man gcc 啰~呵呵!可多的跟天書一樣~
### 21.2.3 調用外部函數庫:加入鏈接的函數庫
剛剛我們都僅只是在屏幕上面印出一些字眼而已,如果說要計算數學公式呢?例如我們想要計算出三角函數里面的 sin (90度角)。要注意的是,大多數的程序語言都是使用徑度而不是一般我們在計算的“角度”, 180 度角約等于 3.14 徑度!嗯!那我們就來寫一下這個程序吧!
```
[root@study ~]# vim sin.c
#include <stdio.h>
#include <math.h>
int main(void)
{
float value;
value = sin ( 3.14 / 2 );
printf("%f\n",value);
}
```
上面這個文件的內容可以在下面取得!
* [http://linux.vbird.org/linux_basic/0520source/sin.c](http://linux.vbird.org/linux_basic/0520source/sin.c)
那要如何編譯這支程序呢?我們先直接編譯看看:
```
[root@study ~]# gcc sin.c
# 新的 GCC 會主動將函數抓進來給你用,所以只要加上 include <math.h> 就好了!
```
新版的 GCC 會主動幫你將所需要的函數庫抓進來編譯,所以不會出現怪異的錯誤訊息! 事實上,數學函數庫使用的是 libm.so 這個函數庫,你最好在編譯的時候將這個函數庫納進去比較好~另外要注意, 這個函數庫放置的地方是系統默認會去找的 /lib, /lib64 ,所以你無須使用下面的 -L 去加入搜尋的目錄! 而 libm.so 在編譯的寫法上,使用的是 -lm (lib 簡寫為 l 喔!) 喔!因此就變成:
* 編譯時加入額外函數庫鏈接的方式:
```
[root@study ~]# gcc sin.c -lm -L/lib -L/lib64 <==重點在 -lm
[root@study ~]# ./a.out <==嘗試執行新文件!
1.000000
```
特別注意,使用 gcc 編譯時所加入的那個 -lm 是有意義的,他可以拆開成兩部份來看:
* -l :是“加入某個函數庫(library)”的意思,
* m :則是 libm.so 這個函數庫,其中, lib 與擴展名(.a 或 .so)不需要寫
所以 -lm 表示使用 libm.so (或 libm.a) 這個函數庫的意思~至于那個 -L 后面接的路徑呢?這表示: “我要的函數庫 libm.so 請到 /lib 或 /lib64 里面搜尋!”
上面的說明很清楚了吧!不過,要注意的是,由于 Linux 默認是將函數庫放置在 /lib 與 /lib64 當中,所以你沒有寫 -L/lib 與 -L/lib64 也沒有關系的!不過,萬一哪天你使用的函數庫并非放置在這兩個目錄下,那么 -L/path 就很重要了!否則會找不到函數庫喔!
除了鏈接的函數庫之外,你或許已經發現一個奇怪的地方,那就是在我們的 sin.c 當中第一行“ #include <stdio.h>”,這行說的是要將一些定義數據由 stdio.h 這個文件讀入,這包括 printf 的相關設置。這個文件其實是放置在 /usr/include/stdio.h 的!那么萬一這個文件并非放置在這里呢?那么我們就可以使用下面的方式來定義出要讀取的 include 文件放置的目錄:
```
[root@study ~]# gcc sin.c -lm -I/usr/include
```
-I/path 后面接的路徑( Path )就是設置要去搜尋相關的 include 文件的目錄啦!不過,同樣的,默認值是放置在 /usr/include 下面,除非你的 include 文件放置在其他路徑,否則也可以略過這個項目!
通過上面的幾個小范例,你應該對于 gcc 以及源代碼有一定程度的認識了,再接下來,我們來稍微整理一下 gcc 的簡易使用方法吧!
### 21.2.4 gcc 的簡易用法 (編譯、參數與鏈結)
前面說過, gcc 為 Linux 上面最標準的編譯器,這個 gcc 是由 [GNU 計劃](http://www.gnu.org/)所維護的,有興趣的朋友請自行前往參考。既然 gcc 對于 Linux 上的 Open source 是這么樣的重要,所以下面我們就列舉幾個 gcc 常見的參數,如此一來大家應該更容易了解源代碼的各項功能吧!
```
# 僅將源代碼編譯成為目標文件,并不制作鏈接等功能:
[root@study ~]# gcc -c hello.c
# 會自動的產生 hello.o 這個文件,但是并不會產生 binary 可執行文件。
# 在編譯的時候,依據作業環境給予最優化執行速度
[root@study ~]# gcc -O hello.c -c
# 會自動的產生 hello.o 這個文件,并且進行最優化喔!
# 在進行 binary file 制作時,將鏈接的函數庫與相關的路徑填入
[root@study ~]# gcc sin.c -lm -L/lib -I/usr/include
# 這個指令較常下達在最終鏈接成 binary file 的時候,
# -lm 指的是 libm.so 或 libm.a 這個函數庫文件;
# -L 后面接的路徑是剛剛上面那個函數庫的搜尋目錄;
# -I 后面接的是源代碼內的 include 文件之所在目錄。
# 將編譯的結果輸出成某個特定文件名
[root@study ~]# gcc -o hello hello.c
# -o 后面接的是要輸出的 binary file 文件名
# 在編譯的時候,輸出較多的訊息說明
[root@study ~]# gcc -o hello hello.c -Wall
# 加入 -Wall 之后,程序的編譯會變的較為嚴謹一點,所以警告訊息也會顯示出來!
```
比較重要的大概就是這一些。另外,我們通常稱 -Wall 或者 -O 這些非必要的參數為旗標 (FLAGS),因為我們使用的是 C 程序語言,所以有時候也會簡稱這些旗標為 CFLAGS ,這些變量偶爾會被使用的喔!尤其是在后頭會介紹的 make 相關的用法時,更是重要的很吶! ^_^
- 鳥哥的Linux私房菜:基礎學習篇 第四版
- 目錄及概述
- 第零章、計算機概論
- 0.1 電腦:輔助人腦的好工具
- 0.2 個人電腦架構與相關設備元件
- 0.3 數據表示方式
- 0.4 軟件程序運行
- 0.5 重點回顧
- 0.6 本章習題
- 0.7 參考資料與延伸閱讀
- 第一章、Linux是什么與如何學習
- 1.1 Linux是什么
- 1.2 Torvalds的Linux發展
- 1.3 Linux當前應用的角色
- 1.4 Linux 該如何學習
- 1.5 重點回顧
- 1.6 本章習題
- 1.7 參考資料與延伸閱讀
- 第二章、主機規劃與磁盤分區
- 2.1 Linux與硬件的搭配
- 2.2 磁盤分區
- 2.3 安裝Linux前的規劃
- 2.4 重點回顧
- 2.5 本章習題
- 2.6 參考資料與延伸閱讀
- 第三章、安裝 CentOS7.x
- 3.1 本練習機的規劃--尤其是分區參數
- 3.2 開始安裝CentOS 7
- 3.3 多重開機安裝流程與管理(Option)
- 3.4 重點回顧
- 3.5 本章習題
- 3.6 參考資料與延伸閱讀
- 第四章、首次登陸與線上求助
- 4.1 首次登陸系統
- 4.2 文字模式下指令的下達
- 4.3 Linux系統的線上求助man page與info page
- 4.4 超簡單文書編輯器: nano
- 4.5 正確的關機方法
- 4.6 重點回顧
- 4.7 本章習題
- 4.8 參考資料與延伸閱讀
- 第五章、Linux 的文件權限與目錄配置
- 5.1 使用者與群組
- 5.2 Linux 文件權限概念
- 5.3 Linux目錄配置
- 5.4 重點回顧
- 5.5 本章練習
- 5.6 參考資料與延伸閱讀
- 第六章、Linux 文件與目錄管理
- 6.1 目錄與路徑
- 6.2 文件與目錄管理
- 6.3 文件內容查閱
- 6.4 文件與目錄的默認權限與隱藏權限
- 6.5 指令與文件的搜尋
- 6.6 極重要的復習!權限與指令間的關系
- 6.7 重點回顧
- 6.8 本章習題:
- 6.9 參考資料與延伸閱讀
- 第七章、Linux 磁盤與文件系統管理
- 7.1 認識 Linux 文件系統
- 7.2 文件系統的簡單操作
- 7.3 磁盤的分區、格式化、檢驗與掛載
- 7.4 設置開機掛載
- 7.5 內存交換空間(swap)之創建
- 7.6 文件系統的特殊觀察與操作
- 7.7 重點回顧
- 7.8 本章習題 - 第一題一定要做
- 7.9 參考資料與延伸閱讀
- 第八章、文件與文件系統的壓縮,打包與備份
- 8.1 壓縮文件的用途與技術
- 8.2 Linux 系統常見的壓縮指令
- 8.3 打包指令: tar
- 8.4 XFS 文件系統的備份與還原
- 8.5 光盤寫入工具
- 8.6 其他常見的壓縮與備份工具
- 8.7 重點回顧
- 8.8 本章習題
- 8.9 參考資料與延伸閱讀
- 第九章、vim 程序編輯器
- 9.1 vi 與 vim
- 9.2 vi 的使用
- 9.3 vim 的額外功能
- 9.4 其他 vim 使用注意事項
- 9.5 重點回顧
- 9.6 本章練習
- 9.7 參考資料與延伸閱讀
- 第十章、認識與學習BASH
- 10.1 認識 BASH 這個 Shell
- 10.2 Shell 的變量功能
- 10.3 命令別名與歷史命令
- 10.4 Bash Shell 的操作環境:
- 10.5 數據流重導向
- 10.6 管線命令 (pipe)
- 10.7 重點回顧
- 10.8 本章習題
- 10.9 參考資料與延伸閱讀
- 第十一章、正則表達式與文件格式化處理
- 11.1 開始之前:什么是正則表達式
- 11.2 基礎正則表達式
- 11.3 延伸正則表達式
- 11.4 文件的格式化與相關處理
- 11.5 重點回顧
- 11.6 本章習題
- 11.7 參考資料與延伸閱讀
- 第十二章、學習 Shell Scripts
- 12.1 什么是 Shell scripts
- 12.2 簡單的 shell script 練習
- 12.3 善用判斷式
- 12.4 條件判斷式
- 12.5 循環 (loop)
- 12.6 shell script 的追蹤與 debug
- 12.7 重點回顧
- 12.8 本章習題
- 第十三章、Linux 帳號管理與 ACL 權限設置
- 13.1 Linux 的帳號與群組
- 13.2 帳號管理
- 13.3 主機的細部權限規劃:ACL 的使用
- 13.4 使用者身份切換
- 13.5 使用者的特殊 shell 與 PAM 模塊
- 13.6 Linux 主機上的使用者訊息傳遞
- 13.7 CentOS 7 環境下大量創建帳號的方法
- 13.8 重點回顧
- 13.9 本章習題
- 13.10 參考資料與延伸閱讀
- 第十四章、磁盤配額(Quota)與進階文件系統管理
- 14.1 磁盤配額 (Quota) 的應用與實作
- 14.2 軟件磁盤陣列 (Software RAID)
- 14.3 邏輯卷軸管理員 (Logical Volume Manager)
- 14.4 重點回顧
- 14.5 本章習題
- 14.6 參考資料與延伸閱讀
- 第十五章、例行性工作調度(crontab)
- 15.1 什么是例行性工作調度
- 15.2 僅執行一次的工作調度
- 15.3 循環執行的例行性工作調度
- 15.4 可喚醒停機期間的工作任務
- 15.5 重點回顧
- 15.6 本章習題
- 第十六章、程序管理與 SELinux 初探
- 16.1 什么是程序 (process)
- 16.2 工作管理 (job control)
- 16.3 程序管理
- 16.4 特殊文件與程序
- 16.5 SELinux 初探
- 16.6 重點回顧
- 16.7 本章習題
- 16.8 參考資料與延伸閱讀
- 第十七章、認識系統服務 (daemons)
- 17.1 什么是 daemon 與服務 (service)
- 17.2 通過 systemctl 管理服務
- 17.3 systemctl 針對 service 類型的配置文件
- 17.4 systemctl 針對 timer 的配置文件
- 17.5 CentOS 7.x 默認啟動的服務簡易說明
- 17.6 重點回顧
- 17.7 本章習題
- 17.8 參考資料與延伸閱讀
- 第十八章、認識與分析登錄文件
- 18.1 什么是登錄文件
- 18.2 rsyslog.service :記錄登錄文件的服務
- 18.3 登錄文件的輪替(logrotate)
- 18.4 systemd-journald.service 簡介
- 18.5 分析登錄文件
- 18.6 重點回顧
- 18.7 本章習題
- 18.8 參考資料與延伸閱讀
- 第十九章、開機流程、模塊管理與 Loader
- 19.1 Linux 的開機流程分析
- 19.2 核心與核心模塊
- 19.3 Boot Loader: Grub2
- 19.4 開機過程的問題解決
- 19.5 重點回顧
- 19.6 本章習題
- 19.7 參考資料與延伸閱讀
- 第二十章、基礎系統設置與備份策略
- 20.1 系統基本設置
- 20.2 服務器硬件數據的收集
- 20.3 備份要點
- 20.4 備份的種類、頻率與工具的選擇
- 20.5 鳥哥的備份策略
- 20.6 災難復原的考慮
- 20.7 重點回顧
- 20.8 本章習題
- 20.9 參考資料與延伸閱讀
- 第二十一章、軟件安裝:源代碼與 Tarball
- 20.1 開放源碼的軟件安裝與升級簡介
- 21.2 使用傳統程序語言進行編譯的簡單范例
- 21.3 用 make 進行宏編譯
- 21.4 Tarball 的管理與建議
- 21.5 函數庫管理
- 21.6 檢驗軟件正確性
- 21.7 重點回顧
- 21.8 本章習題
- 21.9 參考資料與延伸閱讀
- 第二十二章、軟件安裝 RPM, SRPM 與 YUM
- 22.1 軟件管理員簡介
- 22.2 RPM 軟件管理程序: rpm
- 22.3 YUM 線上升級機制
- 22.4 SRPM 的使用 : rpmbuild (Optional)
- 22.5 重點回顧
- 22.6 本章習題
- 22.7 參考資料與延伸閱讀
- 第二十三章、X Window 設置介紹
- 23.1 什么是 X Window System
- 23.2 X Server 配置文件解析與設置
- 23.3 顯卡驅動程序安裝范例
- 23.4 重點回顧
- 23.5 本章習題
- 23.6 參考資料與延伸閱讀
- 第二十四章、Linux 核心編譯與管理
- 24.1 編譯前的任務:認識核心與取得核心源代碼
- 24.2 核心編譯的前處理與核心功能選擇
- 24.3 核心的編譯與安裝
- 24.4 額外(單一)核心模塊編譯
- 24.5 以最新核心版本編譯 CentOS 7.x 的核心
- 24.6 重點回顧
- 24.7 本章習題
- 24.8 參考資料與延伸閱讀