是由“編譯器驅動”(compiler driver)完成的:
unix> gcc -o hello hello.c
在這里,gcc的編譯器驅動程序讀取源文件hello.c,
- #include <stdio.h>
- int main()
- {
- printf("hello, world/n");
- return 0;
- }
並把它翻譯成一個可執行目標文件hello,這個過程是分為四個階段完成的。如下圖所示,執行這四個階段的程序(預處理器、編譯器、匯編器和鏈接器)一起構成了編譯系統。
預處理階段:預處理器(cpp)根據以字符#開頭的命令(directives),修改原始的C程序。如hello.c中第一行的#include <stdio.h>指令,它告訴預處理器讀取系統頭文件stdio.h的內容,並把它直接插入到程序文本中去。結果就得到另一個c程序,通常是以.i作為文件擴展名的。
編譯階段:編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s,它包括一個匯編語言程序。匯編語言程序中的每一條語句都一種標准的文本格式,確切地描述了一條低級機器語言指令。匯編語言為不同的高級語言的不同編譯器提供了通用的輸出語言。
匯編階段:匯編器(as)將hello.s翻譯成機器語言指令,再將這些指令打包成一種叫“可重定位”(relocatable)目標程序的格式,並將結果保存在目標文件hello.o中。hello.o文件是一個二進制文件,它的字節編碼是機器語言指令而不是字符。如果用文本編輯器打開,看到的將是一群亂碼。
鏈接階段:hello程序調用了printf函數(標准C庫中的一個函數,每個C編譯器都提供),printf函數存在於一個名為printf.o的單獨的預編譯目標文件中,而這個文件必須以某種方式並入到hello.o程序中。鏈接器(ld) 就負責處理這種並入,結果得到一個可執行目標文件/hello文件(或稱為可執行文件)。可執行文件加載到存儲器后,由系統負責執行。
原文地址:http://blog.csdn.net/lychee007/article/details/4123130
———————————————————————————————————————————————————————————————————————————————
gcc編譯過程
現代編譯器常見的編譯過程:
源文件-->預處理-->編譯/優化-->匯編-->鏈接-->可執行文件
對於gcc而言:
第一步 預處理
命令: gcc -o test.i -E test.c
或者 cpp -o test.i test.c (這里cpp不是值c plus plus,而是the C Preprocessor)
結果: 生成預處理后的文件test.i(可以打開后與預處理前進行比對,當然長度會嚇你一跳)
作用: 讀取c源程序,對偽指令和特殊符號進行處理。包括宏,條件編譯,包含的頭文件,以及一些特殊符號。基本上是一個replace的過程。
第二步 編譯及優化
命令: gcc -o test.s -S test.i
或者 /路徑/cc1 -o test.s test.i
結果: 生成匯編文件test.s(可打開后查看源文件生成的匯編碼)
作用: 通過詞法和語法分析,確認所有指令符合語法規則(否則報編譯錯),之后翻譯成對應的中間碼,在linux中被稱為RTL(Register Transfer Language),通常是平台無關的,這個過程也被稱為編譯前端。編譯后端對RTL樹進行裁減,優化,得到在目標機上可執行的匯編代碼。gcc采用as作為其匯編器,所以匯編碼是AT&T格式的,而不是Intel格式,所以在用gcc編譯嵌入式匯編時,也要采用AT&T格式。
第三步 匯編
命令: gcc -o test.o -c test.s
或者 as -o test.o test.s
結果: 生成目標機器指令文件test.o(可用objdump查看)
作用: 把匯編語言代碼翻譯成目標機器指令, 用file test.o 可以看到test.o是一個relocatable的ELF文件,通常包含.text .rodata代碼段和數據段。可用readelf -r test.o查看需要relocation的部分。
第四步 鏈接
命令: gcc -o test test.o
或者 ld -o test test.o
結果: 生成可執行文件test (可用objdump查看)
作用: 將在一個文件中引用的符號同在另外一個文件中該符號的定義鏈接起來,使得所有的這些目標文件鏈接成為一個能被操作系統加載到內存的執行體。(如果有不到的符號定義,或者重復定義等,會報鏈接錯)。用file test 可以看到test是一個executable的ELF文件。
當然鏈接的時候還會用到靜態鏈接庫,和動態連接庫。靜態庫和動態庫都是.o目標文件的集合。
靜態庫:
命令:ar -v -q test.a test.o
結果: 生成靜態鏈接庫test.a
作用: 靜態庫是在鏈接過程中將相關代碼提取出來加入可執行文件的庫(即在鏈接的時候將函數的代碼將從其所在地靜態鏈接庫中被拷貝到最終的可執行程序中),ar只是將一些別的文件集合到一個文件中。可以打包,當然也可以解包。
動態庫:
命令: gcc -shared test.so test.o
或者/PATH/collect2 -shared test.so test.o (省略若干參數)
結果: 生成動態連接庫test.so
作用: 動態庫在鏈接時只創建一些符號表,而在運行的時候才將有關庫的代碼裝入內存,映射到運行時相應進程的虛地址空間。如果出錯,如找不到對應的.so文件,會在執行的時候報動態連接錯(可用LD_LIBRARY_PATH指定路徑)。用file test.so可以看到test.so是shared object的ELF文件。
當然以上各步可以一步或若干步一起完成,如gcc -o test test.c直接得到可執行文件。
附:
ELF文件格式
ELF文件格式是ABI(Application Binary Interface)的一部分,被Tool Interface Standards committee作為在32位Intel架構下可移植的目標文件格式。其格式比較復雜,這里就不細講了,只說說其類型。
在specification 1.1中定義了的類型。表示在ELF header中的e_type
Name Value Meaning
==== ===== =======
ET_NONE 0 No file type
ET_REL 1 Relocatable file
ET_EXEC 2 Executable file
ET_DYN 3 Shared object file
ET_CORE 4 Core file
ET_LOPROC 0xff00 Processor-specific
ET_HIPROC 0xffff Processor-specific
主要的有4種
1. Relocatable file 保留了代碼和數據,被用來和其他的object file一起創建可執行的文件或者是shared object file. (也就是我們常見的.o文件)
2. Executable file 保留了用來執行的程序,該文件可以被系統exec()加載用以創建程序進程。(也就是我們常說的可執行文件)
3. Shared object file 保留了代碼和數據,以在兩種情況下被連接,一是link editor如ld,可以用它與其他的Relocateble或者Shared的object file一起創建另一個object file. 二是與Executable file或者其他的Shared object file動態鏈接成為一個進程映像。(也就是我們常說的動態鏈接庫,或者.so文件)
4. Core file 的內容在規范中沒有指明,目前多用來記錄core dump信息。
原文地址:http://blog.csdn.net/lychee007/article/details/4123018