編譯流程圖
?
編譯過程
1.預(yù)處理(Preprocessing),
2.編譯(Compilation),
3.匯編(Assemble),
4.鏈接(Linking)。
?
如下簡單入門程序:
// hello.c
#include <stdio.h>
int main()
{
? ? ? printf("hello world!\n");
}
通過gcc編譯
$ gcc hello.c # 編譯
$ ./a.out # 執(zhí)行
hello world!
預(yù)處理
第一個階段是預(yù)處理階段,在正式的編譯階段之前進(jìn)行。預(yù)處理階段將根據(jù)已放置在文件中的預(yù)處理指令來修改源文件的內(nèi)容抽诉。
1) 宏定義指令,如# define Name TokenString,# undef等盈滴。
對于前一個偽指令,預(yù)編譯所要做的是將程序中的所有Name用TokenString替換轿钠,但作為字符串常量的 Name則不被替換巢钓。對于后者,則將取消對某個宏的定義疗垛,使以后該串的出現(xiàn)不再被替換症汹。
2) 條件編譯指令,如# ifdef贷腕,# ifndef背镇,# else,# elif泽裳,# endif等瞒斩。
這些偽指令的引入使得程序員可以通過定義不同的宏來決定編譯程序?qū)δ男┐a進(jìn)行處理。預(yù)編譯程序?qū)⒏鶕?jù)有關(guān)的文件涮总,將那些不必要的代碼過濾掉胸囱。
3) 頭文件包含指令,如# include "FileName" 或者# include < FileName> 等瀑梗。
在頭文件中一般用偽指令# define定義了大量的宏(最常見的是字符常量)烹笔,同時包含有各種外部符號的聲明裳扯。
采用頭文件的目的主要是為了使某些定義可以供多個不同的C源程序使用。因為在需要用到這些定義的C源程序中谤职,只需加上一條# include語句即可嚎朽,而不必再在此文件中將這些定義重復(fù)一遍。預(yù)編譯程序?qū)杨^文件中的定義統(tǒng)統(tǒng)都加入到它所產(chǎn)生的輸出文件中柬帕,以供編譯程序?qū)χM(jìn)行處理哟忍。包含到c源程序中的頭文件可以是系統(tǒng)提供的,這些頭文件一般被放在/ usr/ include目錄下陷寝。在程序中# include它們要使用尖括號(< >)锅很。另外開發(fā)人員也可以定義自己的頭文件,這些文件一般與c源程序放在同一目錄下凤跑,此時在# include中要用雙引號("")爆安。
4) 特殊符號,預(yù)編譯程序可以識別一些特殊的符號仔引。
例如在源程序中出現(xiàn)的LINE標(biāo)識將被解釋為當(dāng)前行號(十進(jìn)制數(shù))扔仓,F(xiàn)ILE則被解釋為當(dāng)前被編譯的C源程序的名稱。預(yù)編譯程序?qū)τ谠谠闯绦蛑谐霈F(xiàn)的這些串將用合適的值進(jìn)行替換咖耘。
預(yù)編譯程序所完成的基本上是對源程序的“替代”工作翘簇。經(jīng)過此種替代,生成一個沒有宏定義儿倒、沒有條件編譯指令版保、沒有特殊符號的輸出文件。這個文件的含義同沒有經(jīng)過預(yù)處理的源文件是相同的夫否,但內(nèi)容有所不同彻犁。下一步,此輸出文件將作為編譯程序的輸入而被翻譯成為機器指令凰慈。
編譯
經(jīng)過預(yù)編譯得到的輸出文件中汞幢,只有常量;如數(shù)字微谓、字符串森篷、變量的定義,以及C語言的關(guān)鍵字堰酿,如main, if , else , for , while , { , } , + , - , * , \ 等等疾宏。編譯程序所要作得工作就是通過詞法分析和語法分析张足,在確認(rèn)所有的指令都符合語法規(guī)則之后触创,將其翻譯成等價的中間代碼表示或匯編代碼。優(yōu)化處理是編譯系統(tǒng)中一項比較艱深的技術(shù)为牍。它涉及到的問題不僅同編譯技術(shù)本身有關(guān)哼绑,而且同機器的硬件環(huán)境也有很大的關(guān)系岩馍。優(yōu)化一部分是對中間代碼的優(yōu)化。這種優(yōu)化不依賴于具體的計算機抖韩。另一種優(yōu)化則主要針對目標(biāo)代碼的生成而進(jìn)行的蛀恩。對于前一種優(yōu)化,主要的工作是刪除公共表達(dá)式茂浮、循環(huán)優(yōu)化(代碼外提双谆、強度削弱、變換循環(huán)控制條件席揽、已知量的合并等)顽馋、復(fù)寫傳播,以及無用賦值的刪除幌羞,等等寸谜。后一種類型的優(yōu)化同機器的硬件結(jié)構(gòu)密切相關(guān),最主要的是考慮是如何充分利用機器的各個硬件寄存器存放有關(guān)變量的值属桦,以減少對于內(nèi)存的訪問次數(shù)熊痴。另外,如何根據(jù)機器硬件執(zhí)行指令的特點(如流水線聂宾、RISC果善、CISC、VLIW等)而對指令進(jìn)行一些調(diào)整使目標(biāo)代碼比較短系谐,執(zhí)行的效率比較高岭埠,也是一個重要的研究課題。經(jīng)過優(yōu)化得到的匯編代碼必須經(jīng)過匯編程序的匯編轉(zhuǎn)換成相應(yīng)的機器指令蔚鸥,方可能被機器執(zhí)行惜论。
匯編
匯編過程實際上指把匯編語言代碼翻譯成目標(biāo)機器指令的過程。對于被翻譯系統(tǒng)處理的每一個C語言源程序止喷,都將最終經(jīng)過這一處理而得到相應(yīng)的目標(biāo)文件馆类。目標(biāo)文件中所存放的也就是與源程序等效的目標(biāo)的機器語言代碼。
目標(biāo)文件由段組成弹谁。通常一個目標(biāo)文件中至少有兩個段:
1) 代碼段:該段中所包含的主要是程序的指令乾巧。該段一般是可讀和可執(zhí)行的,但一般卻不可寫预愤。
2) 數(shù)據(jù)段:主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據(jù)沟于。一般數(shù)據(jù)段都是可讀,可寫植康,可執(zhí)行的旷太。
UNIX環(huán)境下主要有三種類型的目標(biāo)文件:
1) 可重定位文件
其中包含有適合于其它目標(biāo)文件鏈接來創(chuàng)建一個可執(zhí)行的或者共享的目標(biāo)文件的代碼和數(shù)據(jù)。
2) 共享的目標(biāo)文件
這種文件存放了適合于在兩種上下文里鏈接的代碼和數(shù)據(jù)。第一種是鏈接程序可把它與其它可重定位文件及共享的目標(biāo)文件一起處理來創(chuàng)建另一個目標(biāo)文件供璧;第二種是動態(tài)鏈接程序?qū)⑺c另一個可執(zhí)行文件及其它的共享目標(biāo)文件結(jié)合到一起存崖,創(chuàng)建一個進(jìn)程映象。
3) 可執(zhí)行文件
它包含了一個可以被操作系統(tǒng)創(chuàng)建一個進(jìn)程來執(zhí)行之的文件睡毒。匯編程序生成的實際上是第一種類型的目標(biāo)文件来惧。對于后兩種還需要其他的一些處理方能得到,這個就是鏈接程序的工作了演顾。
鏈接
由匯編程序生成的目標(biāo)文件并不能立即就被執(zhí)行供搀,其中可能還有許多沒有解決的問題。
例如钠至,某個源文件中的函數(shù)可能引用了另一個源文件中定義的某個符號(如變量或者函數(shù)調(diào)用等)趁曼;在程序中可能調(diào)用了某個庫文件中的函數(shù),等等棕洋。所有的這些問題挡闰,都需要經(jīng)鏈接程序的處理方能得以解決。
鏈接程序的主要工作就是將有關(guān)的目標(biāo)文件彼此相連接掰盘,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來摄悯,使得所有的這些目標(biāo)文件成為一個能夠被操作系統(tǒng)裝入執(zhí)行的統(tǒng)一整體。
1) 靜態(tài)鏈接
在這種鏈接方式下愧捕,函數(shù)的代碼將從其所在的靜態(tài)鏈接庫中被拷貝到最終的可執(zhí)行程序中奢驯。這樣該程序在被執(zhí)行時這些代碼將被裝入到該進(jìn)程的虛擬地址空間中。靜態(tài)鏈接庫實際上是一個目標(biāo)文件的集合次绘,其中的每個文件含有庫中的一個或者一組相關(guān)函數(shù)的代碼瘪阁。
2) 動態(tài)鏈接
在此種方式下,函數(shù)的代碼被放到稱作是動態(tài)鏈接庫或共享對象的某個目標(biāo)文件中邮偎。鏈接程序此時所作的只是在最終的可執(zhí)行程序中記錄下共享對象的名字以及其它少量的登記信息管跺。在此可執(zhí)行文件被執(zhí)行時,動態(tài)鏈接庫的全部內(nèi)容將被映射到運行時相應(yīng)進(jìn)程的虛地址空間禾进。動態(tài)鏈接程序?qū)⒏鶕?jù)可執(zhí)行程序中記錄的信息找到相應(yīng)的函數(shù)代碼豁跑。
實例
預(yù)處理器:將.c 文件轉(zhuǎn)化成 .i文件,使用的gcc命令是:gcc –E泻云,對應(yīng)于預(yù)處理命令cpp艇拍;
編譯器:將.c/.h文件轉(zhuǎn)換成.s文件,使用的gcc命令是:gcc –S宠纯,對應(yīng)于編譯命令 cc –S卸夕;
匯編器:將.s 文件轉(zhuǎn)化成 .o文件,使用的gcc命令是:gcc –c婆瓜,對應(yīng)于匯編命令是 as快集;
鏈接器:將.o文件轉(zhuǎn)化成可執(zhí)行程序,使用的gcc命令是: gcc,對應(yīng)于鏈接命令是 ld碍讨;
加載器:將可執(zhí)行程序加載到內(nèi)存并進(jìn)行執(zhí)行治力,loader和ld-linux.so蒙秒。
/ test.c
#include <stdio.h>
#include "mymath.h"http:// 自定義頭文件
int main(){
? ? int a = 2;
? ? int b = 3;
? ? int sum = add(a, b);
? ? printf("a=%d, b=%d, a+b=%d\n", a, b, sum);
}
// mymath.h
#ifndef MYMATH_H
#define MYMATH_H
int add(int a, int b){
? ? return a+b;
}
int sub(int a, int b){
? ? return a-b;
}
#endif