CNU 的 gcc 編譯器

GNU 程式設計

GNU 程式簡介

GNU 與 C 語言

gcc 編譯器

glibc 函式庫

make 建置

ld 連結器

as 組譯器

ar 函式庫

objdump

objcopy

訊息

相關網站

參考文獻

最新修改

簡體版

English

簡介

GNU 的編譯器 gcc 被廣泛使用在各種處理器上,支援的平台非常眾多,因此,是很好的編譯工具。

編譯器 gcc 是 GNU 工具的核心程式,除了可以編譯 C 語言之外,也提供將 C 語言轉換為組合語言的功能,甚至可以直接組譯組合語言,這些功能對學習系統程式有很大的幫助,因為 gcc 讓我們可以將編譯、組譯、連結等動作合併或分開進行,讓我們得以觀察許多中間過程,以便理解系統程式的編譯、組譯、連結等觀念。

在本文中,我們將示範如何使用 gcc 進行跨平台的編譯,以及如何進行最佳化等動作。透過這樣的實作,讀者應該會對編譯器的實務有更進一步的理解。

gcc 的跨平台編譯

由於 GNU 工具所支援的處理器眾多,因此在嵌入式系統的市場,gcc 的佔有率相當高。許多嵌入式開發者都會使用 gcc 作為跨平台編譯器,以開發嵌入式裝置,像是手機、MP3、機上盒、數位相框等裝置的程式。圖 1 顯示了一個使用 gcc 進行跨平台編譯的範例,該範例的開發環境是一台個人電腦。我們可以使用 gcc 編譯出在個人電腦上執行的組合語言 (或目的檔),但是也可以用跨平台編譯器,像是 arm-elf-gcc,編譯出可在ARM處理器上執行的目的檔。然後再透過燒錄或上傳的方式,將該目的檔放到目標板上執行。

GccCrossCompiler.jpg

圖 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
    .file    "hello.c"
.globl _hello
    .data
_hello:
    .ascii "hello !\0"
    .def    ___main;.scl 2; .type 32; .endef
    .text
.globl _main
    .def    _main; .scl 2; .type 32; .endef
_main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    andl    $-16, %esp
    movl    $0, %eax
    addl    $15, %eax
    addl    $15, %eax
    shrl    $4, %eax
    sall    $4, %eax
    movl    %eax, -4(%ebp)
    movl    -4(%ebp), %eax
    call    __alloca
    call    ___main
    movl    $_hello, (%esp)
    call    _printf
    movl    $1, %eax
    leave
    ret
    .def _printf; .scl 3; .type 32; .endef
指令:arm-elf-gcc -S hello.c -o hello_ARM.s
檔案:hello_ARM.s
 .file "hello.c"
.gcc2_compiled.:
    .global    hello
.data
    .align    2
    .type     hello,object
    .size     hello,8
hello:
    .ascii    "hello !\000"
.text
    .align    2
    .global    main
    .type     main,function
main:
    mov    ip, sp
    stmfd    sp!, {fp, ip, lr, pc}
    sub    fp, ip, #4
    bl    __gccmain
    ldr    r0, .L3
    bl    printf
    mov    r0, #1
    b    .L2
.L4:
    .align    2
.L3:
    .word    hello
.L2:
    ldmea    fp, {fp, sp, pc}
.Lfe1:
    .size     main,.Lfe1-main

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 (無最佳化)
    .file    "optimize.c"
    .def ___main; .scl 2; .type 32;    .endef
    .section .rdata,"dr"
LC0:
    .ascii "x=%d\12\0"
    .text
.globl _main
    .def _main; .scl    2; .type 32; .endef
_main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    andl    $-16, %esp
    movl    $0, %eax
    addl    $15, %eax
    addl    $15, %eax
    shrl    $4, %eax
    sall    $4, %eax
    movl    %eax, -8(%ebp)
    movl    -8(%ebp), %eax
    call    __alloca
    call    ___main
    call    _f
    movl    %eax, -4(%ebp)
    movl    -4(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    $LC0, (%esp)
    call    _printf
    leave
    ret
.globl _f
    .def _f; .scl 2; .type 32; .endef
_f:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    $3, -4(%ebp)
    movl    $4, -8(%ebp)
    movl    -8(%ebp), %eax
    addl    -4(%ebp), %eax
    movl    %eax, -12(%ebp)
    movl    -8(%ebp), %eax
    addl    -4(%ebp), %eax
    movl    %eax, -16(%ebp)
    movl    -16(%ebp), %eax
    addl    -12(%ebp), %eax
    leave
    ret
    .def _printf; .scl 3; .type 32; .endef
    .def _f; .scl 3; .type 32; .endef
(b) 編譯指令(O3級最佳化):
指令:gcc -S optimize.c -o optimize_O3.s -O3
檔案:optimize_O3.s (有最佳化)
.file    "optimize.c"
    .text
    .p2align 4,,15
.globl _f
    .def _f; .scl 2; .type 32; .endef
_f:
    pushl    %ebp
    movl    $14, %eax
    movl    %esp, %ebp
    popl    %ebp
    ret
    .def ___main; .scl 2; .type 32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "x=%d\12\0"
    .text
    .p2align 4,,15
.globl _main
    .def _main; .scl    2; .type 32; .endef
_main:
    pushl    %ebp
    movl    $16, %eax
    movl    %esp, %ebp
    subl    $8, %esp
    andl    $-16, %esp
    call    __alloca
    call    ___main
    movl    $LC0, (%esp)
    movl    $14, %eax
    movl    %eax, 4(%esp)
    call    _printf
    leave
    ret
    .def    _printf; .scl 3; .type 32; .endef
    .def _f; .scl 3; .type 32; .endef

在編譯器最佳化的議題上,有許多相關的研究與技術,若要更深入的理解這些技術,請進一步參考編譯器的相關書籍。

參考文獻

  1. Using and Porting the GNU Compiler Collection (GCC) — http://gcc.gnu.org/onlinedocs/gcc-2.95.3/gcc.html
  2. 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.

Facebook

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License