以helloworld爲例分析gcc編譯過程:
#include
int main()
{
printf(“Hello World\n”);
return 0;
}
通常我們使用gcc來生成可執行程序,命令爲:gcc hello.c,生成可執行文件a.out
實際上gcc hello.c可以分解爲4個步驟,分別是預處理(Preprocess),編譯(Compilation),彙編(Assembly)和鏈接(Linking)。
1 預編譯
gcc –E hello.c –o hello.i,以下爲預處理後的輸出文件hello.i的內容
# 1 "hello.c"
# 1 ""
# 1 ""
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
/***** 省略了部分內容,包括stdio.h中的一些聲明及定義 *****/
# 2 "hello.c" 2
int main()
{
printf("Hello World\n");
return 0;
}
預編譯過程主要處理那些源代碼中以#開始的預編譯指令,主要處理規則如下:
l 將所有的#define刪除,並且展開所有的宏定義;
l 處理所有條件編譯指令,如#if,#ifdef等;
l 處理#include預編譯指令,將被包含的文件插入到該預編譯指令的位置。該過程遞歸進行,及被包含的文件可能還包含其他文件。
l 刪除所有的註釋//和 /**/;
l 添加行號和文件標識,如#2 “hello.c” 2,以便於編譯時編譯器產生調試用的行號信息及用於編譯時產生編譯錯誤或警告時能夠顯示行號信息;
l 保留所有的#pragma編譯器指令,因爲編譯器須要使用它們;
2 編譯
編譯過程就是把預處理完的文件進行一系列詞法分析,語法分析,語義分析及優化後生成相應的彙編代碼文件。
gcc –S hello.i –o hello.s,以下爲編譯後的輸出文件hello.s的內容
.file "hello.c"
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.4.0 20090506 (Red Hat 4.4.0-4)"
.section .note.GNU-stack,"",@progbits
3 彙編
彙編器是將彙編代碼轉變成機器可以執行的命令,每一個彙編語句幾乎都對應一條機器指令。彙編相對於編譯過程比較簡單,根據彙編指令和機器指令的對照表一一翻譯即可。
gcc –c hello.c –o hello.o,由於hello.o的內容爲機器碼,不能以文本形式方便的呈現。
4 鏈接
鏈接器ld將各個目標文件組裝在一起,解決符號依賴,庫依賴關係,並生成可執行文件。
ld –static crt1.o crti.o crtbeginT.o hello.o –start-group –lgcc –lgcc_eh –lc-end-group crtend.o crtn.o (省略了文件的路徑名)。