程序的生命周期從一個高級C語言程序開始,這種形式能夠被人讀懂,卻不能被機器讀懂,為了在系統上運行這個程序,該源程序需要被其他程序轉化為一系列低級機器語言指令,然後將這些指令按照可執行目標程序的格式打包並以二進制磁盤文件形式存儲起來。
在Linux系統下,可用以下指令完成源程序到目標程序的轉化:
gcc -o hello hello.c main.c
gcc 編譯器驅動程序讀取源文件hello.c和main.c,經過預處理、編譯、匯編、鏈接(分別使用預處理器、編譯器、匯編器、鏈接器,這四個程序構成了編譯系統)四個步驟,將其翻譯成可執行目標程序hello。如下圖所示:
運行以下命令: » gcc –help
如下圖所示,分別對應上圖四個階段:
//main.c
#include<stdio.h>
void hello();
int main()
{
hello();
return 0;
}
//hello.c
#include<stdio.h>
void hello()
{
printf("Hello world\n");
}
預處理器(CPP)根據源程序中以字符”#”開頭的命令,修改源程序,得到另一個源程序,常以.i作為文件擴展名。修改主要包括#include、#define和條件編譯三個方面。
可執行以下命令查看程序變化:
gcc -o main.i -E main.c gcc -o hello.i -E hello.c
查看hello.i,如下圖所示(main.i類似):
從上圖可以看出,預處理只是對源文件進行了擴展,得到的仍然是C語言源程序。
編譯器(CCL)將經過預處理器處理得到的文本文件hello.i和main.i翻譯成hello.s與main.s,其中包含了匯編語言程序,匯編語言程序以一種標准的文本格式確切描述一條低級機器語言指令。
運行以下命令進行編譯:
gcc -S main.i hello.i
查看main.s和hello.s:
//main.s
.file "main.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
call hello
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu8) 4.8.1"
.section .note.GNU-stack,"",@progbits
//hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "Hello world"
.text
.globl hello
.type hello, @function
hello:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl $.LC0, (%esp)
call puts
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size hello, .-hello
.ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu8) 4.8.1"
.section .note.GNU-stack,"",@progbits
匯編器(AS)將hello.s和main.s翻譯成機器語言指令,並打包成可重定位目標程序,一般以.o為文件擴展名。可重定位目標程序是二進制文件,它的字節編碼是機器語言指令而不是字符。
運行以下指令可得到重定位目標程序main.o和hello.o:
gcc -c main.s hello.s
用文本編輯器打開main.o和hello.o發現文件是亂碼,因為此時已經是二進制文件。
鏈接程序(LD)將main.o和hello.o以及一些其他必要的目標文件組合起來,創建可執行目標文件。
gcc -o hello main.o hello.o
得到可執行程序hello.
在終端運行./hello,程序加載並運行。