簡介
GNU 的編譯器 gcc 被廣泛使用在各種處理器上,支援的平台非常眾多,因此,是很好的編譯工具。
編譯器 gcc 是 GNU 工具的核心程式,除了可以編譯 C 語言之外,也提供將 C 語言轉換為組合語言的功能,甚至可以直接組譯組合語言,這些功能對學習系統程式有很大的幫助,因為 gcc 讓我們可以將編譯、組譯、連結等動作合併或分開進行,讓我們得以觀察許多中間過程,以便理解系統程式的編譯、組譯、連結等觀念。
在本文中,我們將示範如何使用 gcc 進行跨平台的編譯,以及如何進行最佳化等動作。透過這樣的實作,讀者應該會對編譯器的實務有更進一步的理解。
gcc 的跨平台編譯
由於 GNU 工具所支援的處理器眾多,因此在嵌入式系統的市場,gcc 的佔有率相當高。許多嵌入式開發者都會使用 gcc 作為跨平台編譯器,以開發嵌入式裝置,像是手機、MP3、機上盒、數位相框等裝置的程式。圖 1 顯示了一個使用 gcc 進行跨平台編譯的範例,該範例的開發環境是一台個人電腦。我們可以使用 gcc 編譯出在個人電腦上執行的組合語言 (或目的檔),但是也可以用跨平台編譯器,像是 arm-elf-gcc,編譯出可在ARM處理器上執行的目的檔。然後再透過燒錄或上傳的方式,將該目的檔放到目標板上執行。
圖 1. 使用gcc跨平台編譯的範例圖
現在,請使用者撰寫一個簡單的C語言程式,如範例 1 所示,該範例是一個可以印出 “hello!” 字樣的程式。
範例 1. C語言程式 – hello.c
char hello[]="hello !";
int main() {
printf(hello);
return 1;
}
接著,讓我們看看真實的編譯器是如何將函數轉換為組合語言的,我們利用 gcc加上 -S 參數的方式,將C程式 hello.c 編譯為組合語言。如果您安裝了專為 ARM CPU 所製作的跨平台 gcc 編譯器 ,那麼,您就可以使用 arm-elf-gcc 指令,將 hello.c 編譯為 ARM 的組合語言。範例 2 顯示了分別用 gcc 與 arm-elf-gcc 將 hello.c 編譯為組合語言的例子。由於我們是在安裝有MS. WindowsXP作業系統的IA32平台上,透過Cygwin環境操作的,因此,gcc 預設的輸出即是IA32的組合語言與目的檔,這稱不上是跨平台編譯器。
但是,當我們使用 arm-elf-gcc 進行編譯時,我們所輸出的 hello_ARM.s 就是 ARM 處理器的組合語言,而 hello_ARM.o 則是 ARM 處理器的目的檔,這種編譯器,就被稱為跨平台編譯器 (Cross Compiler)。因為,我們在 IA32 的平台上,編譯出的卻是 ARM 的目的檔,這在嵌入式系統的開發上相當常見。嵌入式系統程式設計師在開發的時候,通常桌上會有一塊目標板,然後在桌上型電腦上,編譯出目標板的程式,再透過傳輸線上傳至目標板進行測試。
範例 2. 在cygwin中分別以 gcc 與 arm-elf-gcc編譯 hello.c
$ gcc -S hello.c -o hello_IA32.s
$ arm-elf-gcc -S hello.c -o hello_ARM.s
$ gcc hello_IA32.s -o hello_IA32.o
$ arm-elf-gcc hello_ARM.s -o hello_ARM.o
$ ./hello_IA32.o
hello !
$ ./hello_ARM.o
./hello_ARM.o: ./hello_ARM.o: cannot execute binary file
為了讓讀者更清楚跨平台編譯與一般編譯的差別,我們在範例 3 同時列出了 ARM 與 IA32 兩個版本的組合語言檔,其中 hello_IA32.s 是本機編譯器 gcc 的輸出檔。而 hello_ARM.s 則是跨平台編譯器 arm-elf-gcc 的輸出檔,讀者可以稍為比較一下兩者的不同點,並且搭配前兩節的 IA32 與 ARM 的說明,觀察一下兩種組合語言的不同點,這對理解處理器的結構有所幫助。
範例 3. IA32 與 ARM 組合語言程式對照 (hello.c)
指令:gcc -S hello.c -o hello_IA32.s 檔案:hello_IA32.s
|
指令:arm-elf-gcc -S hello.c -o hello_ARM.s 檔案:hello_ARM.s
|
Gcc的最佳化功能
GNU 的 gcc 編譯器,是 GNU 工具中最重要的程式之一,gcc 提供了四個層級的最佳化功能,包含完全不最佳化,以及 -O1, -O2, -O3等不同等級的最佳化功能,讓我們利用 –S 參數,將最佳化的結果以組合語言輸出,真槍實彈的觀察看看gcc的最佳化結果。
範例 4 顯示了一個具有函數 f() 的C語言程式,然而,函數f雖然做了一些計算,但傳回的值固定為14,我們用這個函數來測試 gcc 的最佳化能力,看看 gcc 最佳化能做到何種程度。
我們分別用 -O0 的無最佳化與 -O3 的最高等級最佳化進行編譯,其結果如範例 5 所示,讀者可以看到在第 2 欄的 optimize_O3.s 中,整個 f() 函數都不見了,只剩下movl $14, %eax 這個指令,直接將 f() 的傳回值 14 塞入到 %eax 暫存器,然後傳給 printf() 函數印出,這顯示了 gcc 的 –O3 編譯方式具有較好的最佳化能力。
範例 4. gcc 不同層級的最佳化實例 (optimize.c)
int main() {
int x = f();
printf("x=%d\n", x);
}
int f() {
int a=3, b=4, c, d;
c=a+b;
d=a+b;
return c+d;
}
範例 5. gcc 不同層級的最佳化實例 (optimize.c)
(a) 編譯指令(無最佳化): 指令:gcc -S optimize.c -o optimize_O0.s -O0 檔案:optimize_O0.s (無最佳化)
|
(b) 編譯指令(O3級最佳化): 指令:gcc -S optimize.c -o optimize_O3.s -O3 檔案:optimize_O3.s (有最佳化)
|
在編譯器最佳化的議題上,有許多相關的研究與技術,若要更深入的理解這些技術,請進一步參考編譯器的相關書籍。
參考文獻
- Using and Porting the GNU Compiler Collection (GCC) — http://gcc.gnu.org/onlinedocs/gcc-2.95.3/gcc.html
- ARM CPU 的GNU 工具下載點位於http://www.gnuarm.com/files.html,筆者於寫作時其下載檔案連結為http://www.gnuarm.com/bu-2.15_gcc-3.4.3-c-c++-java_nl-1.12.0_gi-6.1.exe , 筆者存取時間點為 4/24/2009.
Post preview:
Close preview