前言
代碼寫的多了,就想返璞歸真,看看底層的一些東西,于是乎就復(fù)習(xí)了下編譯過程,看到這篇文章寫的簡單明了,一時起了轉(zhuǎn)載知心,希望原文博主,不要介意。
正文開始
在linux系統(tǒng)上,從源文件到目標(biāo)文件的轉(zhuǎn)化是由編譯器完成的。以hello.c程序的編譯為例,如下:
gcc -o hello hello.c
在這里,gcc編譯器讀取源文件hello.c,并把它翻譯成一個可執(zhí)行文件 hello。
這個翻譯過程可分為四個階段逐步完成:預(yù)處理,編譯,匯編,鏈接,如下圖所示。
逐步做下簡單分析:
在未編譯前,hello.c 的源代碼如下
#include <stdio.h>
int main()
{
printf("hello, world\n");
}
第一步、預(yù)處理階段
執(zhí)行命令: gcc -o hello.i -E hello.c
或者cpp -o hello.i hello.c
(這里cpp不是指c plus plus,而是預(yù)處理器the C Preprocessor)預(yù)處理器cpp根據(jù)以字符開頭#開頭的命令,修改原始C程序。比如hello.c中的第一行為 #include <stdio.h>,預(yù)處理器便將stdio.h的內(nèi)容直接插入到程序中。預(yù)處理之后得到文本文件hello.i,打開如下
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
...
...
...
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 940 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
int main()
{
printf("hello, world\n");
}
在源代碼的前面插入了stdio.h,整個hello.i 的行數(shù)由hello.c的6行變到了855行!(不同的編譯環(huán)境可能不同)
第二步、編譯階段
執(zhí)行命令: gcc -o hello.s -S hello.i
或者ccl -o hello.s hello.i
編譯器ccl 將文本文件hello.i 翻譯為hello.s,這個文件里面包含一個匯編程序,如下
.file "hello.c"
.section .rodata
.LC0:
.string "hello, world"
.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
subl $16, %esp
movl $.LC0, (%esp)
call puts
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",@progbits
匯編語言是非常有用的,因為它將不同高級語言的不同編譯器提供了通用的輸出語言。例如,C和Fortran 的在此步編譯產(chǎn)生的輸出文件都是一樣的匯編語言。
第三步、匯編階段
執(zhí)行命令: gcc -o hello.o -c hello.s
或者 as -o hello.o hello.s
匯編器as 將hello.s 翻譯成機器語言保存在hello.o 中。這是個二進制文件,用命令hexdump hello.o 打開如下
457f 464c 0101 0001 0000 0000 0000 0000
0001 0003 0001 0000 0000 0000 0000 0000
011c 0000 0000 0000 0034 0000 0000 0028
000d 000a 8955 83e5 f0e4 ec83 c710 2404
0000 0000 fce8 ffff c9ff 00c3 6568 6c6c
2c6f 7720 726f 646c 0000 4347 3a43 2820
6255 6e75 7574 4c2f 6e69 7261 206f 2e34
2e36 2d33 7531 7562 746e 3575 2029 2e34
2e36 0033 0014 0000 0000 0000 7a01 0052
7c01 0108 0c1b 0404 0188 0000 001c 0000
00000a0 001c 0000 0000 0000 0017 0000 4100 080e
00000b0 0285 0d42 5305 0cc5 0404 0000 2e00 7973
00000c0 746d 6261 2e00 7473 7472 6261 2e00 6873
00000d0 7473 7472 6261 2e00 6572 2e6c 6574 7478
00000e0 2e00 6164 6174 2e00 7362 0073 722e 646f
00000f0 7461 0061 632e 6d6f 656d 746e 2e00 6f6e
6574 472e 554e 732d 6174 6b63 2e00 6572
2e6c 6865 665f 6172 656d 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
*
0000 0000 001f 0000 0001 0000 0006 0000
0000 0000 0034 0000 0017 0000 0000 0000
0000 0000 0004 0000 0000 0000 001b 0000
0009 0000 0000 0000 0000 0000 03e8 0000
0010 0000 000b 0000 0001 0000 0004 0000
0008 0000 0025 0000 0001 0000 0003 0000
00001a0 0000 0000 004c 0000 0000 0000 0000 0000
第四步、鏈接階段
執(zhí)行命令: gcc -o hello hello.o
或者 ld -o hello hello.o
注意!hello程序調(diào)用了printf 函數(shù),這個函數(shù)是標(biāo)準(zhǔn)C庫中的一個函數(shù),他保存在一個名為printf.o 的文件中,這個文件必須以某種方式合并到我們的hello.o的程序中。鏈接器ld 負責(zé)處理這種合并。結(jié)果得到hello 可執(zhí)行文件,可以被加載到內(nèi)存中由系統(tǒng)執(zhí)行。
./hello
總結(jié)
編譯器的編譯過程:
源文件-->預(yù)處理-->編譯/優(yōu)化-->匯編-->鏈接-->可執(zhí)行文件。
平常用的最多、看起來一句命令就搞定的一次編譯背后其實非常不簡單。此博作簡單記錄,能力到了之后再做深入分析。
參考文獻
《Computer Systems: A Programmer's Perspective》 by Randal Bryant.
“編譯過程” by itwilliamlee, http://www.lisdn.com/html/95/n-15195.html