一個(gè)可執(zhí)行文件的生成一般都要經(jīng)過(guò)下面幾個(gè)步驟:
編輯 仲锄、預(yù)處理 扔嵌、 編譯、優(yōu)化本昏、匯編 供汛、 連接 ——>可執(zhí)行文件
下面將從這幾個(gè)步驟一個(gè)一個(gè)來(lái)分析他們的具體內(nèi)容。
編輯
編輯這個(gè)過(guò)程其實(shí)挺簡(jiǎn)單的,但也是最講究的怔昨,它直接體現(xiàn)了一個(gè)編程者的編程習(xí)慣雀久,以及影響到別人對(duì)程序的閱讀感受,所以有必要總結(jié)一下趁舀。
(1) 注釋要規(guī)范岸啡,多用 /...../ ,少用// ,邏輯復(fù)雜的函數(shù)要注明函數(shù)的功能以及每個(gè)參數(shù)的含義赫编,全局變量以及結(jié)構(gòu)體要注明用處
(2) 一定要注意縮進(jìn)巡蘸,tab設(shè)置為4個(gè)空格會(huì)看起來(lái)更緊湊
(3) 分支語(yǔ)句對(duì)應(yīng)的兩個(gè)大括號(hào)要獨(dú)占一行,而且盡量靠近行的開(kāi)頭
(4) 注意程序的結(jié)構(gòu)性和層次性
(5) 大型程序應(yīng)該對(duì)函數(shù)的功能以及模塊進(jìn)行歸類
(6) 使用一個(gè)好的擂送,適合自己的編輯器預(yù)處理
預(yù)處理其實(shí)就是對(duì)所有源代碼進(jìn)行整合的一個(gè)過(guò)程悦荒,它將該程序所涉及到的所有代碼,包括頭文件嘹吨、宏定義搬味、條件編譯和執(zhí)行代碼,都整合為一個(gè)整體蟀拷。
預(yù)處理過(guò)程會(huì)完成以下工作:
(1) 文件包含:包括兩種格式 #include <my.h> #include "my.h"
第一種方法是用尖括號(hào)把頭文件括起來(lái)碰纬,這種格式告訴預(yù)處理程序在編譯器自帶的或外部庫(kù)的頭文件中搜索被包含的頭文件。第二種方法是用雙引號(hào)把頭文件括起來(lái)问芬,這種格式告訴預(yù)處理程序在當(dāng)前被編譯的應(yīng)用程序的源代碼文件中搜索被包含的頭文件悦析,如果找不到,再搜索編譯器自帶的頭文件此衅。在預(yù)處理時(shí)强戴,會(huì)將對(duì)應(yīng)文件的全部?jī)?nèi)容插入并替換該#include語(yǔ)句, 如果這個(gè)頭文件還包含另外一個(gè)頭文件挡鞍,那么另外一個(gè)頭文件也會(huì)先替換調(diào)用它的的#include語(yǔ)句
(2) 宏替換:將函數(shù)中使用到宏的地方骑歹,都使用對(duì)應(yīng)的值進(jìn)行替換,這些值主要是#define 命令聲明的
(3) 條件編譯:將不符合條件編譯的語(yǔ)句刪除墨微,保留符合條件編譯的語(yǔ)句道媚。比如#if 0 ... #endif \ #if defined.... #endif 等條件編譯語(yǔ)句,不符合對(duì)應(yīng)條件的語(yǔ)句將會(huì)被丟掉翘县,而保留符合條件編譯的部分
(4) 特殊符號(hào):預(yù)編譯程序可以識(shí)別一些特殊的符號(hào)最域,例如在源程序中出現(xiàn)的LINE標(biāo)識(shí)將被解釋為當(dāng)前行號(hào)(十進(jìn)制數(shù)),F(xiàn)ILE則被解釋為當(dāng)前被編譯的C源程序的名稱炼蹦。預(yù)編譯程序?qū)τ谠谠闯绦蛑谐霈F(xiàn)的這些串將用合適的值進(jìn)行替換羡宙。
(5) 整理:刪除程序中的注釋和多余的空白字符
3.優(yōu)化階段
優(yōu)化處理是編譯系統(tǒng)中一項(xiàng)比較艱深的技術(shù)。它涉及到的問(wèn)題不僅同編譯技術(shù)本身有關(guān)掐隐,而且同機(jī)器的硬件環(huán)境也有很大的關(guān)系狗热。優(yōu)化一部分是對(duì)中間代碼的優(yōu)化钞馁。這種優(yōu)化不依賴于具體的計(jì)算機(jī)。另一種優(yōu)化則主要針對(duì)目標(biāo)代碼的生成而進(jìn)行的匿刮。上圖中僧凰,我們將優(yōu)化階段放在編譯程序的后面,這是一種比較籠統(tǒng)的表示熟丸。
對(duì)于前一種優(yōu)化训措,主要的工作是刪除公共表達(dá)式、循環(huán)優(yōu)化(代碼外提光羞、強(qiáng)度削弱绩鸣、變換循環(huán)控制條件、已知量的合并等)纱兑、復(fù)寫傳播呀闻,以及無(wú)用賦值的刪除,等等潜慎。后一種類型的優(yōu)化同機(jī)器的硬件結(jié)構(gòu)密切相關(guān)捡多,最主要的是考慮是如何充分利用機(jī)器的各個(gè)硬件寄存器存放的有關(guān)變量的值,以減少對(duì)于內(nèi)存的訪問(wèn)次數(shù)铐炫。另外垒手,如何根據(jù)機(jī)器硬件執(zhí)行指令的特點(diǎn)(如流水線、RISC倒信、CISC科贬、VLIW等)而對(duì)指令進(jìn)行一些調(diào)整使目標(biāo)代碼比較短,執(zhí)行的效率比較高堤结,也是一個(gè)重要的研究課題唆迁。
經(jīng)過(guò)優(yōu)化得到的匯編代碼必須經(jīng)過(guò)匯編程序的匯編轉(zhuǎn)換成相應(yīng)的機(jī)器指令鸭丛,方可能被機(jī)器執(zhí)行竞穷。
4.匯編過(guò)程
匯編過(guò)程實(shí)際上指把匯編語(yǔ)言代碼翻譯成目標(biāo)機(jī)器指令的過(guò)程。對(duì)于被翻譯系統(tǒng)處理的每一個(gè)C語(yǔ)言源程序鳞溉,都將最終經(jīng)過(guò)這一處理而得到相應(yīng)的目標(biāo)文件瘾带。目標(biāo)文件中所存放的也就是與源程序等效的目標(biāo)的機(jī)器語(yǔ)言代碼。 目標(biāo)文件由段組成熟菲。通常一個(gè)目標(biāo)文件中至少有兩個(gè)段:代碼段 該段中所包含的主要是程序的指令看政。該段一般是可讀和可執(zhí)行的,但一般卻不可寫抄罕。 數(shù)據(jù)段 主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據(jù)允蚣。一般數(shù)據(jù)段都是可讀,可寫呆贿,可執(zhí)行的嚷兔。
UNIX環(huán)境下主要有三種類型的目標(biāo)文件:
(1)可重定位文件 其中包含有適合于其它目標(biāo)文件鏈接來(lái)創(chuàng)建一個(gè)可執(zhí)行的或者共享的目標(biāo)文件的代碼和數(shù)據(jù)森渐。
(2)共享的目標(biāo)文件 這種文件存放了適合于在兩種上下文里鏈接的代碼和數(shù)據(jù)。第一種事鏈接程序可把它與其它可重定位文件及共享的目標(biāo)文件一起處理來(lái)創(chuàng)建另一個(gè)目標(biāo)文件冒晰;第二種是動(dòng)態(tài)鏈接程序?qū)⑺c另一個(gè)可執(zhí)行文件及其它的共享目標(biāo)文件結(jié)合到一起同衣,創(chuàng)建一個(gè)進(jìn)程映象。
(3)可執(zhí)行文件 它包含了一個(gè)可以被操作系統(tǒng)創(chuàng)建一個(gè)進(jìn)程來(lái)執(zhí)行之的文件壶运。匯編程序生成的實(shí)際上是第一種類型的目標(biāo)文件耐齐。對(duì)于后兩種還需要其他的一些處理方能得到,這個(gè)就是鏈接程序的工作了蒋情。
5.鏈接程序
由匯編程序生成的目標(biāo)文件并不能立即就被執(zhí)行埠况,其中可能還有許多沒(méi)有解決的問(wèn)題。例如棵癣,某個(gè)源文件中的函數(shù)可能引用了另一個(gè)源文件中定義的某個(gè)符號(hào)(如變量或者函數(shù)調(diào)用等)询枚;在程序中可能調(diào)用了某個(gè)庫(kù)文件中的函數(shù),等等浙巫。所有的這些問(wèn)題金蜀,都需要經(jīng)鏈接程序的處理方能得以解決。
鏈接程序的主要工作就是將有關(guān)的目標(biāo)文件彼此相連接的畴,也即將在一個(gè)文件中引用的符號(hào)同該符號(hào)在另外一個(gè)文件中的定義連接起來(lái)渊抄,使得所有的這些目標(biāo)文件成為一個(gè)能夠被操作系統(tǒng)裝入執(zhí)行的統(tǒng)一整體。
根據(jù)開(kāi)發(fā)人員指定的同庫(kù)函數(shù)的鏈接方式的不同丧裁,鏈接處理可分為兩種:
(1)靜態(tài)鏈接 在這種鏈接方式下护桦,函數(shù)的代碼將從其所在地靜態(tài)鏈接庫(kù)中被拷貝到最終的可執(zhí)行程序中。這樣該程序在被執(zhí)行時(shí)這些代碼將被裝入到該進(jìn)程的虛擬地址空間中煎娇。靜態(tài)鏈接庫(kù)實(shí)際上是一個(gè)目標(biāo)文件的集合二庵,其中的每個(gè)文件含有庫(kù)中的一個(gè)或者一組相關(guān)函數(shù)的代碼。
(2)動(dòng)態(tài)鏈接 在此種方式下缓呛,函數(shù)的代碼被放到稱作是動(dòng)態(tài)鏈接庫(kù)或共享對(duì)象的某個(gè)目標(biāo)文件中催享。鏈接程序此時(shí)所作的只是在最終的可執(zhí)行程序中記錄下共享對(duì)象的名字以及其它少量的登記信息。在此可執(zhí)行文件被執(zhí)行時(shí)哟绊,動(dòng)態(tài)鏈接庫(kù)的全部?jī)?nèi)容將被映射到運(yùn)行時(shí)相應(yīng)進(jìn)程的虛地址空間因妙。動(dòng)態(tài)鏈接程序?qū)⒏鶕?jù)可執(zhí)行程序中記錄的信息找到相應(yīng)的函數(shù)代碼。
對(duì)于可執(zhí)行文件中的函數(shù)調(diào)用票髓,可分別采用動(dòng)態(tài)鏈接或靜態(tài)鏈接的方法攀涵。使用動(dòng)態(tài)鏈接能夠使最終的可執(zhí)行文件比較短小,并且當(dāng)共享對(duì)象被多個(gè)進(jìn)程使用時(shí)能節(jié)約一些內(nèi)存洽沟,因?yàn)樵趦?nèi)存中只需要保存一份此共享對(duì)象的代碼以故。但并不是使用動(dòng)態(tài)鏈接就一定比使用靜態(tài)鏈接要優(yōu)越。在某些情況下動(dòng)態(tài)鏈接可能帶來(lái)一些性能上損害裆操。
經(jīng)過(guò)上述五個(gè)過(guò)程怒详,C源程序就最終被轉(zhuǎn)換成可執(zhí)行文件了
上面5個(gè)步驟分別對(duì)應(yīng)了gcc的幾個(gè)選項(xiàng) -E -S -c 和 ld工具鳄乏, gcc的-o 選項(xiàng)可以看作是一個(gè)重定向選項(xiàng),和shell中的> 類比棘利, -o后面接的文件名就是輸出文件橱野, gcc的輸入文件一般放在命令的最后,或者放在-c的后面
gcc -E:是預(yù)處理選項(xiàng)善玫,比如 gcc -E main.c -o main.E 將會(huì)生成對(duì)應(yīng)源文件的匯編結(jié)果水援,注意預(yù)處理過(guò)程是不產(chǎn)生對(duì)應(yīng)的輸出文件的,它會(huì)將預(yù)處理后的內(nèi)容顯示到屏幕和輸送到編譯階段茅郎,所以如果需要保存預(yù)編譯的內(nèi)容蜗元,需要用-o選項(xiàng)進(jìn)行重定向保存
gcc -S:是編譯選項(xiàng),這個(gè)選項(xiàng)會(huì)將預(yù)處理好的源代碼編譯成匯編語(yǔ)言系冗,比如gcc -S main.c -o main.S 奕扣,注意 -S會(huì)默認(rèn)執(zhí)行-E選項(xiàng)的過(guò)程
gcc -c: 是匯編選項(xiàng),這個(gè)選項(xiàng)將源代碼匯編成對(duì)應(yīng)的目標(biāo)文件(*.o)掌敬,并且以源文件的前綴命名惯豆, 比如gcc -c main.c 將生成 main.o , gcc -c main.S 也將生成main.o文件, 當(dāng)gcc只有這個(gè)選項(xiàng)的時(shí)候?qū)⒛J(rèn)執(zhí)行前面的-E -S選項(xiàng)
ld: ld工具是連接工具奔害,ld -Tmain.lds 0x0000 main.o -o main 它將前面產(chǎn)生的目標(biāo)文件連接成可執(zhí)行文件楷兽,至于目標(biāo)文件,我們也可以使用ar工具或者gcc -shared 制作不同的靜態(tài)庫(kù)和共享庫(kù)
如果編譯一個(gè)源文件時(shí)华临,gcc沒(méi)有帶任何參數(shù)芯杀,那么會(huì)將上面的選項(xiàng)全部執(zhí)行
下面將用一個(gè)實(shí)際例子來(lái)解釋上面的幾個(gè)步驟:
(1) 首先編輯一個(gè)簡(jiǎn)單的文件 main.c
#include <stdio.h>
#define A 1
#define B 2
int main()
{
printf("a+b=%d \n", A, B);
return 0;
}
(2) 執(zhí)行g(shù)cc -E main.c -o main.i ,生成預(yù)處理文件main.i,下面是main.i的內(nèi)容
extern char *ctermid (char *__s) __attribute__ ((__nothrow__));
# 886 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__));
# 916 "/usr/include/stdio.h" 3 4
# 2 "main.c" 2
int main()
{
printf("a+b=%d \n", 1, 2);
return 0;
}
可以看到預(yù)處理階段雅潭,將宏進(jìn)行了替換
(3)執(zhí)行g(shù)cc -S main.c -o main.s 將生成匯編文件main.s
.file "main.c"
.section .rodata
.LC0:
.string "a+b=%d \n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $.LC0, %eax
movl $2, 8(%esp)
movl $1, 4(%esp)
movl %eax, (%esp)
call printf
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
.section .note.GNU-stack,"",@progbits
(4)執(zhí)行 gcc -c main.s -o main.o 將生成目標(biāo)文件 main.o
(5)執(zhí)行l(wèi)d -Tmain.lds -o main 將連接成可執(zhí)行文件 main
main.lds是連接腳本揭厚,它定義了整個(gè)程序編譯之后的連接過(guò)程,決定了一個(gè)可執(zhí)行程序的各個(gè)段的存儲(chǔ)位置扶供,
關(guān)于.lds 的內(nèi)容可自尋查找
轉(zhuǎn)載出處:http://blog.csdn.net/lee244868149/article/details/49536747