用 C 寫一個程式需要些什麼工具?某甲:“編譯器,VC 啦 TC 啦什麼的……”
是吧?不對:)其實這句話首先不完整,其次有邏輯的錯誤。我們需要的不僅僅是一個編譯器;首先我們需要的是一個書寫程式的工具,一般統稱“編輯器”。最簡單的編輯器就是 Windows 內建的寫字板,好一點的有 UltraEdit 或者 EditPlus 之類。在 Linux 下可以使用 VI, Vim 或者大名鼎鼎的 Emacs 作為編輯器。編輯器的作用在於讓你輸入程式,並且儲存為一個普通的文字文件。因此,如果你能記得在儲存的時候選擇“存為文字檔(*.txt)”或者類似的命令(以保證得到的檔案裡面沒有雜七雜八的格式資訊),用
Microsoft Word 或者 MacroMeidia DreamWeaver MX 也沒有問題^_^ C/C++ 的源檔案名稱沒有 Java 的檔案命名機制那麼 BT,但是有幾個常規如下:
·C 源碼檔案尾碼名為 .c
·C++ 源碼檔案尾碼名為 .cpp .cxx .cc,或者在區分檔案名稱大小寫系統上為 .C
·C 標頭檔(表頭檔——我喜歡這個名字)為 .h
·C++ 標頭檔為 .h 或者 .hpp,而標準庫的標頭檔通常沒有尾碼名(如 iostream)
所有的這些尾碼名都不影響檔案本身的內容:所有檔案都是 plain text ——純文字文檔。尾碼名的作用在於提示程式員它所包含的內容,同時可以提示編譯器應該採取的行為。
寫程式不是寫小說。除了編輯器,我們還需要一套工具,把我們寫的程式碼轉換成機器可以執行的二進位格式。這一套工具應該至少包含一個前置處理器,一個編譯器和一個連結器。對於 GNU Binutils/GCC 系列工具,有前置處理器 cpp (C Pre-Processor),編譯器 gcc(GNU C Compiler)和 g++ (GNU C++ Compiler),彙編器 as(The GNU assembler) 和連結器 ld(The
GNU Linker )。這麼多工具,都是幹什麼的?讓我們一個一個瞧瞧看。
·這是一個很經典的 C 程式,傳說中的 Hello, World
gentoo@yuantoo tmp $ cat hello.c
#include <stdio.h>
int main()
{
printf( "Hello, world!\n ");
}
·編譯成 hello.exe
gentoo@yuantoo tmp $ gcc hello.c -o hello.exe
·運行
gentoo@yuantoo tmp $ ./hello.exe
Hello, world!
好,現在看看它到底都做了什麼工作。
第二步中,我用 gcc hello.c -o hello.exe 把 hello.c 編譯成了 hello.exe。這裡其實還有幾個步驟,但是 Gcc 自動的完成了它們。這包括預先處理、編譯和連結。
首先是預先處理。我們可以讓 gcc 在預先處理之後停下:
gentoo@yuantoo tmp $ gcc -E hello.c
結果是,gcc 在螢幕上飛快的列印了無數的看不見的資訊。可以看到最後幾行是這樣的:
extern char *ctermid (char *__s) ;
# 807 "/usr/include/stdio.h " 3 4
extern void flockfile (FILE *__stream) ;
extern int ftrylockfile (FILE *__stream) ;
extern void funlockfile (FILE *__stream) ;
# 831 "/usr/include/stdio.h " 3 4
# 2 "hello.c " 2
int main()
{
printf( "Hello, world!\n ");
}
也就是說,我們的 hello.c 的內容是在最後。而前面的那些東西,都是從 <stdio.h> 以及 stdio.h 的包含檔案中插入進來的資訊。前置處理器主要的工作就是處理所有源碼中以 # 開頭的行,將 #include 指令替換成指令指出的檔案的內容,對 #define 定義的符號進行了文本替換,以及根據符號選擇需要進入結果檔案的內容。我們短短四行字的代碼檔案,經過 gcc 的預先處理,得到了一個 913 行的檔案。
預先處理之後的工作是彙編——這也是真正編譯工作的第一步驟。用 -S 標誌可以讓 gcc 在彙編之後停下來。
gentoo@yuantoo tmp $ gcc -S hello.c
gentoo@yuantoo tmp $ ls
hello.c hello.s
我們得到了一個名為 hello.s 的檔案。看看它的開始部分
gentoo@yuantoo tmp $ head -n10 hello.s
.file "hello.c "
.section .rodata
.LC0:
.string "Hello, world!\n "
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
熟悉吧!都是產生的彙編碼。
產生彙編碼之後的步驟是彙編,把彙編碼轉換成對象檔案。
gentoo@yuantoo tmp $ as hello.s -o hello.o
得到了 hello.o,就是包含 hello.c 代碼的對象代碼。
得到的 hello 還不能運行,我們需要把它和 C 語言運行庫連結起來。它不僅包含了程式的入口,還有 printf 等標準 C 庫函數的實現。
gentoo@yuantoo tmp $ ld -o a.out -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt?.o hello.o -lc
這裡,-o hello.exe 表示輸出為 hello.exe 檔案。-dynamic-linker /lib/ld-linux.so.2 表示將程式動態連結到 /lib/ld-linux.so.2 這個 shared object。這個 shared object 作為作業系統中的一個特殊的庫,它負責引入其他的 so。同時,我們要在我們的程式中包含 /usr/lib 下的幾個 crt?.o 對象檔案:它們中包含了 C 程式的所需要的運行時環境。最後一個參數
-lc 表示將程式連結到標準 C 庫上(名為 libc.so.5,在 /usr/lib 目錄下。ld 會根據設定檔自動搜尋 /usr/lib 目錄;如果庫檔案在其他目錄中,則需要用 -L 參數指出。)執行完畢,我們得到了一個具有執行許可權的 hello.exe 檔案。
gentoo@yuantoo tmp $ ./hello.exe
Hello, world!
gentoo@yuantoo tmp $
就這樣,一個簡單的 C 程式檔案,經過 0、編寫,1、預先處理,2、編譯,3、連結,終於產生了一個可執行檔。一般而言,gcc 編譯器可以替我們完成整個過程,只要簡簡單單一個 gcc -o hello.exe hello.c 命令,三個步驟就可以統統完成。
從上面可以看出,編寫代碼和編譯代碼完全是分離的兩個過程,可以用完全不同的工具替換每個步驟(譬如用 notepad 或者 EditPlus 作編輯器編寫代碼,用 Cygwin 或者 Mingw 做編譯器,等等)。VC 和 TC 都是所謂的 IDE(整合式開發環境),它包含了編輯器、編譯器和調試器,通常還包含了專案管理和文檔產生以及其他一系列協助工具輔助,可以大大簡化項目開發週期。它們既不是編譯器,也不是編輯器;它們包含了這些所有東西。