摘要:總結(jié)上面的過程中,我們已經(jīng)將源程序經(jīng)過預(yù)處理編譯匯編階段變成了二進(jìn)制代碼,這三個過程我們都是用兩種方法完成的,一種是參數(shù)的方法,另一種是使用系統(tǒng)默認(rèn)的預(yù)處理器,編譯器,匯編器。
目錄
1. 程序的翻譯環(huán)境和執(zhí)行環(huán)境
在ANSIC的任何一種實現(xiàn)中,存在兩個不同的環(huán)境。
第1種是翻譯環(huán)境,在這個環(huán)境中源代碼被轉(zhuǎn)換為可執(zhí)行的機器指令。
第2種是執(zhí)行環(huán)境,它用于實際執(zhí)行代碼。
?組成一個程序的每個源文件通過編譯過程分別轉(zhuǎn)換成目標(biāo)代碼(object code)。
每個目標(biāo)文件由鏈接器(linker)捆綁在一起,形成一個單一而完整的可執(zhí)行程序。
鏈接器同時也會引入標(biāo)準(zhǔn)C函數(shù)庫中任何被該程序所用到的函數(shù),而且它可以搜索程序員個人的程序庫,將其需要的函數(shù)也鏈接到程序中。
? 編譯一個 C程序可以分為四階段:預(yù)處理階段?--->?生成匯編代碼階段?--->?匯編階段?--->?鏈接階段。?
gcc 指令的一般格式為:?
gcc [選項] 要編譯的文件 [選項] [目標(biāo)文件] 其中,目標(biāo)文件可缺省,gcc默認(rèn)生成可執(zhí)行的文件名為:a.out gcc main.c 直接生成可執(zhí)行文件 a.out gcc -E main.c -o hello.i 生成預(yù)處理后的代碼(還是文本文件) gcc –S main.c -o hello.s 生成匯編代碼 gcc –c main.c -o hello.o 生成目標(biāo)代碼
C程序?目標(biāo)文件和可執(zhí)行文件?結(jié)構(gòu)
目標(biāo)文件和可執(zhí)行文件可以有幾種不同的格式,有ELF(Excutable and linking Format,可執(zhí)行文件和鏈接)格式,也有COFF(Common Object-File Format,普通目標(biāo)文件格式)。
雖然格式不一樣,但具有一個共同的概念,那就是?段(segments),這里段指二進(jìn)制格式文件中的一塊區(qū)域。
linux下的可執(zhí)行文件有三個段:(?可用?nm?命令查看目標(biāo)文件的符號清單?)
預(yù)編譯:主要處理那些源代碼文件中的以?#?開始的預(yù)編譯指令,如?#include、#define、#if,同時并刪除注釋行,還會添加行號和文件名標(biāo)識,以便于編譯時編譯器產(chǎn)生調(diào)試用的行號信息,及用于編譯時產(chǎn)生編譯錯誤或警告時能夠顯示行號。
經(jīng)過預(yù)編譯的 .i 文件不包含任何宏定義,因為所有的宏已經(jīng)被展開并且包含的文件也已經(jīng)被插入到 .i 文件中。
所以當(dāng)我們無法判斷?宏定義是否正確?或?頭文件包含是否正確?時,可以查看已編譯后的文件來確認(rèn)問題。比如:hello.c 中第一行的 #include
用法:#gcc -E main.c -o main.i作用:將main.c預(yù)處理輸出main.i文件[user:test] lsmain.c[user:test] gcc -E main.c -o main.i[user:test] lsmain.c main.i
使用 gcc?-E 參數(shù)完成。
預(yù)處理會干什么事情:
?處理完成之后看看我們的 Hello.i,發(fā)現(xiàn)原來8行代碼現(xiàn)在變成了接近700行,因為將
使用系統(tǒng)默認(rèn)的預(yù)處理器 cpp 完成。
預(yù)處理除了使用 GCC -E 參數(shù)完成之外,我們還可以使用系統(tǒng)默認(rèn)的預(yù)處理器 cpp 完成。如下所示
我們看看Hello.ii的代碼:
雖然 Hello.i 和 Hello.ii 的代碼對應(yīng)的行數(shù)不同,但是內(nèi)容卻是一模一樣的,只是中間空行的數(shù)量不同而已。
OK ,接下來,繼續(xù)向編譯出發(fā)。
gcc -S
編譯是將?源文件?轉(zhuǎn)換成?匯編代碼?的過程,具體的步驟主要有:詞法分析 ---> 語法分析 ---> 語義分析及相關(guān)的優(yōu)化 ---> 中間代碼生成 ---> 目標(biāo)代碼生成(匯編文件.s)。
具體生成過程可以參考《編譯原理》。在這個階段中,gcc 首先要檢查代碼的規(guī)范性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤后,gcc 把代碼翻譯成匯編語言。
用戶可以使用?-S 選項來進(jìn)行查看,該選項只進(jìn)行編譯而不進(jìn)行匯編,生成匯編代碼。
選項 -S用法:[user]# gcc –S main.i –o main.s作用:將預(yù)處理輸出文件main.i匯編成main.s文件。[user:test] lsmain.c main.i[user:test] gcc -S main.i -o main.s[user:test] lsmain.c main.i main.s
注意:gcc 命令只是一個后臺程序的包裝,會根據(jù)不同的參數(shù)要求去調(diào)用預(yù)編譯編譯程序cc1(c)、匯編器 as、連接器 ld。
使用 gcc?-S 參數(shù)完成。
查看 Hello.s 發(fā)現(xiàn)已經(jīng)是匯編代碼了。
使用系統(tǒng)默認(rèn)的編譯器 cc1 完成這個過程。
前面的預(yù)處理命令?cpp?
可能大家的系統(tǒng)上都有,我們輸入cp
,然后?Tab?
兩下(Linux系統(tǒng)上表示提示補全命令),系統(tǒng)提示如下:?
倒數(shù)第二個命令就是?cpp?
了。但是我們?cc?
同樣的過程的時候卻發(fā)現(xiàn):?
并沒有?cc1?
這個命令,但是?cc1?
確實是?Linux?
系統(tǒng)上默認(rèn)的編譯器呀,我們在系統(tǒng)上找找看:?
看上圖第二條,/usr/libexec/gcc/x86_64-redhat-linux/4.8.2/cc1
,嘗試著去看下:?
有可執(zhí)行權(quán)限,那為何不試試能不能用來編譯?Hello.ii?
呢??
好像沒有什么報錯,迫不及待的看看?Hello.ss?
的內(nèi)容:
發(fā)現(xiàn)和?Hello.s?
的是一樣的。編譯成功。
匯編階段是把編譯階段生成的 ”.s” 文件轉(zhuǎn)成二進(jìn)制目標(biāo)代碼。匯編器(as)將 hello.s 翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目標(biāo)程序的格式,并將結(jié)果保存在目標(biāo)文件hello.o中。hello.o文件是一個二進(jìn)制文件,它的字節(jié)編碼是機器語言指令而不是字符。如果我們在文本編譯器中打開 hello.o 文件,看到的將是一堆亂碼。
選項 -c用法:[user]# gcc -c main.s -o main.o作用:將匯編輸出文件main.s編譯輸出main.o文件。[user:test] lsmain.c main.i main.s[user:test] gcc -c main.s -o main.o[user:test] lsmain.c main.i main.o main.s
使用 gcc?-c 參數(shù)完成。
其實也可以查看下 Hello.o 的內(nèi)容:
只是亂碼罷了。要是想看,我們可以使用 hexedit, readelf 和 objdump 這三個工具。
hexedit 只是個將二進(jìn)制文件用十六進(jìn)制打開的工具,我們執(zhí)行:
$ sudo yum install hexedit$ hexedit Hello.o
可以看到:
最右邊是源文件被翻譯成可見字符,點.表示的都是不可見字符。這樣看當(dāng)然沒有多大實際意義,但是一些輸出的字符串 Hello World,包括整個文件的類型 ELF 都是可以看到的。
readelf 和 objdump 我們后面再說。
使用系統(tǒng)默認(rèn)的匯編器as完成。
hexedit 看看 :
使用 cmp 命令比較 Hello.oo 和 Hello.o
只有極少數(shù)字符不同。可能也是格式問題。
總結(jié):上面的過程中,我們已經(jīng)將 Hello.c 源程序經(jīng)過預(yù)處理、編譯、匯編階段變成了二進(jìn)制代碼,這三個過程我們都是用兩種方法完成的,一種是 GCC + 參數(shù)的方法,另一種是使用系統(tǒng)默認(rèn)的預(yù)處理器,編譯器,匯編器。這兩種方法都達(dá)到了我們的目的,最后給它加上x權(quán)限。然后運行
chmod a+x a.out./a.out
這階段就是把匯編后的機器指令集變成可以直接運行的文件,而對目標(biāo)文件進(jìn)行鏈接主要是因為在目標(biāo)文件中可能用到了在其他文件當(dāng)中定義的字段(或者函數(shù)),通過鏈接來把多個不同目標(biāo)文件關(guān)聯(lián)到一起。
比如:有2個目標(biāo)文件 a 和 b,在 b 中定義了一個函數(shù) "method",而在文件 a 中則使用到了b文件中的函數(shù) "method",通過鏈接文件a才能調(diào)用到函數(shù)"method",不然文件a根本就不知道到函數(shù) "method" 底做了些什么操作。
hello 程序調(diào)用了一個 printf 函數(shù),它是每個 C 編譯器都會提供的標(biāo)準(zhǔn)C庫中的一個函數(shù),printf 函數(shù)存在于一個名為 printf.o 的多帶帶預(yù)編譯好了的標(biāo)準(zhǔn)文件中,而這個文件必須以某種方式合并到我們的 hello.o 程序中,鏈接器(ld)就負(fù)責(zé)處理這種合并,結(jié)果就得到 hello 文件,他是一個可執(zhí)行目標(biāo)文件(簡稱:可執(zhí)行文件),可以被加載到內(nèi)存中,有系統(tǒng)執(zhí)行。
gcc的無選項的編譯就是鏈接用法:[user]# gcc main.o -o main.elf作用:將編譯輸出文件main.o鏈接成最終可執(zhí)行文件main.elf[user:test] lsmain.c main.i main.o main.s[user:test] gcc main.o -o main.elf[user:test] lsmain.c main.elf* main.i main.o main.s
模塊之間的通信有兩種方式:一種是模塊間的函數(shù)調(diào)用,另一種是模塊間的變量訪問。函數(shù)訪問需知道目標(biāo)函數(shù)的地址,變量訪問也需要知道目標(biāo)變量的地址,所以這兩種方式都可以歸結(jié)為一種方式,那就是模塊間符號的引用。模塊間依靠符號來通信類似于拼圖版,定義符號的模塊多出一塊區(qū)域,引用該符號的模塊剛好少了那一塊區(qū)域,兩者一拼接剛好完美組合。這個模塊的拼接過程就是“鏈接”。
在鏈接中,函數(shù)和變量統(tǒng)稱為符號(symbol),函數(shù)名或變量名就是符號名(symbol name)。可以將符號看做是鏈接中的粘合劑,整個鏈接過程正是基于符號才能夠正確完成。鏈接過程中很關(guān)鍵的一部分就是符號的管理,每一個目標(biāo)文件都會有一個相應(yīng)的符號表(symbol table),這個表里面記錄了目標(biāo)文件中所用到的所有符號。每個定義的符號有一個對應(yīng)的值,叫做符號值(symbol value),對于變量和函數(shù)來說,符號值就是它們的地址。符號表中所有的符號分類:
鏈接過程主要包括了地址和空間分配、符號決議和重定位。符號決議有時候也叫做符號綁定、名稱綁定、名稱決議,甚至還有叫做地址綁定、指令綁定,大體上它們的意思都一樣,但從細(xì)節(jié)角度來區(qū)分,它們之間還存在一定區(qū)別,比如“決議”更傾向于靜態(tài)鏈接,而“綁定”更傾向于動態(tài)鏈接,即它們所使用的范圍不一樣。
每個目標(biāo)文件都可能定義一些符號,也可能引用到定義咋其他目標(biāo)文件的符號。重定位的過程中,每個重定位的入口都是對一個符號的引用,那么當(dāng)鏈接器須要對某個符號的引用重定位時,它就是要確定這個符號的目標(biāo)地址。這時候鏈接器就會去查找由所有輸入目標(biāo)文件的符號表組成的全局符號表,找到相應(yīng)的符號后進(jìn)行重定位。
看代碼:
sum.c
int g_val = 2016;void print(const char *str){printf("%s/n", str);}
test.c
#include int main(){extern void print(char *str);extern int g_val;printf("%d/n", g_val);print("hello bit./n");return 0;}
?如何查看編譯期間的每一步發(fā)生了什么呢?
test.c
#include int main(){int i = 0;for(i=0; i<10; i++){printf("%d ", i);}return 0;}
1. 預(yù)處理 選項 gcc -E test.c -o test.i
預(yù)處理完成之后就停下來,預(yù)處理之后產(chǎn)生的結(jié)果都放在test.i文件中。
2. 編譯 選項 gcc -S test.c
編譯完成之后就停下來,結(jié)果保存在test.s中。
3. 匯編 gcc -c test.c
匯編完成之后就停下來,結(jié)果保存在test.o中。
程序執(zhí)行的過程:
1. 程序必須載入內(nèi)存中。在有操作系統(tǒng)的環(huán)境中:一般這個由操作系統(tǒng)完成。在獨立的環(huán)境中,程序
的載入必須由手工安排,也可能是通過可執(zhí)行代碼置入只讀內(nèi)存來完成。
2. 程序的執(zhí)行便開始。接著便調(diào)用main函數(shù)。
3. 開始執(zhí)行程序代碼。這個時候程序?qū)⑹褂靡粋€運行時堆棧(stack),存儲函數(shù)的局部變量和返回
地址。程序同時也可以使用靜態(tài)(static)內(nèi)存,存儲于靜態(tài)內(nèi)存中的變量在程序的整個執(zhí)行過程
一直保留他們的值。
4. 終止程序。正常終止main函數(shù);也有可能是意外終止。
__FILE__ //進(jìn)行編譯的源文件__LINE__ //文件當(dāng)前的行號__DATE__ //文件被編譯的日期__TIME__ //文件被編譯的時間__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
這些預(yù)定義符號都是語言內(nèi)置的。
舉個栗子:
printf("file:%s line:%d/n", __FILE__, __LINE__);
語法:#define name stuff
??舉個栗子:
#define MAX 1000#define reg register //為 register這個關(guān)鍵字,創(chuàng)建一個簡短的名字#define do_forever for(;;) //用更形象的符號來替換一種實現(xiàn)#define CASE break;case //在寫case語句的時候自動把 break寫上。// 如果定義的 stuff過長,可以分成幾行寫,除了最后一行外,每行的后面都加一個反斜杠(續(xù)行符)。#define DEBUG_PRINT printf("file:%s/tline:%d/t /date:%s/ttime:%s/n" ,/__FILE__,__LINE__ , /__DATE__,__TIME__ )
在define定義標(biāo)識符的時候,要不要在最后加上 ; ?
比如:
#define MAX 1000;#define MAX 1000
建議不要加上 ; ,這樣容易導(dǎo)致問題。
比如下面的場景:
if(condition)max = MAX;elsemax = 0;
這里會出現(xiàn)語法錯誤。
?
#define 機制包括了一個規(guī)定,允許把參數(shù)替換到文本中,這種實現(xiàn)通常稱為宏(macro)或定
義宏(define macro)。
下面是宏的申明方式:
#define name( parament-list ) stuff
?其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現(xiàn)在stuff中。
注意:
參數(shù)列表的左括號必須與name緊鄰。
如果兩者之間有任何空白存在,參數(shù)列表就會被解釋為stuff的一部分。
如:
?
#define SQUARE( x ) x * x
這個宏接收一個參數(shù) x .
如果在上述聲明之后,你把
SQUARE( 5 );
置于程序中,預(yù)處理器就會用下面這個表達(dá)式替換上面的表達(dá)式:
5 * 5
警告:
這個宏存在一個問題:
觀察下面的代碼段:
int a = 5;printf("%d/n" ,SQUARE( a + 1) );
乍一看,你可能覺得這段代碼將打印36這個值。
事實上,它將打印11.
為什么?
替換文本時,參數(shù)x被替換成a + 1,所以這條語句實際上變成了:printf ("%d/n",a + 1 * a + 1 );
這樣就比較清晰了,由替換產(chǎn)生的表達(dá)式并沒有按照預(yù)想的次序進(jìn)行求值。
在宏定義上加上兩個括號,這個問題便輕松的解決了:
?
#define SQUARE(x) (x) * (x)
這樣預(yù)處理之后就產(chǎn)生了預(yù)期的效果:
printf ("%d/n",(a + 1) * (a + 1) );
這里還有一個宏定義:
#define DOUBLE(x) (x) + (x)
定義中我們使用了括號,想避免之前的問題,但是這個宏可能會出現(xiàn)新的錯誤。
int a = 5;printf("%d/n" ,10 * DOUBLE(a));
這將打印什么值呢?
warning:
看上去,好像打印100,但事實上打印的是55.
我們發(fā)現(xiàn)替換之后:
printf ("%d/n",10 * (5) + (5));
乘法運算先于宏定義的加法,所以出現(xiàn)了? ? 55
這個問題,的解決辦法是在宏定義表達(dá)式兩邊加上一對括號就可以了。
#define DOUBLE( x) ( ( x ) + ( x ) )
提示:
所以用于對數(shù)值表達(dá)式進(jìn)行求值的宏定義都應(yīng)該用這種方式加上括號,避免在使用宏時由于參數(shù)
中的操作符或鄰近操作符之間不可預(yù)料的相互作用。
?
在程序中擴展#define定義符號和宏時,需要涉及幾個步驟。
1. 在調(diào)用宏時,首先對參數(shù)進(jìn)行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先
被替換。
2. 替換文本隨后被插入到程序中原來文本的位置。對于宏,參數(shù)名被他們的值替換。
3. 最后,再次對結(jié)果文件進(jìn)行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復(fù)上
述處理過程。
注意:
1. 宏參數(shù)和#define 定義中可以出現(xiàn)其他#define定義的變量。但是對于宏,不能出現(xiàn)遞歸。
2. 當(dāng)預(yù)處理器搜索#define定義的符號的時候,字符串常量的內(nèi)容并不被搜索。
如何把參數(shù)插入到字符串中?
首先我們看看這樣的代碼:
char* p = "hello ""bit/n";printf("hello"," bit/n");printf("%s", p);
這里輸出的是不是? ? ?hello bit? ??
答案是確定的:是。
我們發(fā)現(xiàn)字符串是有自動連接的特點的。
1. 那我們是不是可以寫這樣的代碼?:
#define PRINT(FORMAT, VALUE)/printf("the value is "FORMAT"/n", VALUE);...PRINT("%d", 10);
這里只有當(dāng)字符串作為宏參數(shù)的時候才可以把字符串放在字符串中。
1. 另外一個技巧是:
使用 # ,把一個宏參數(shù)變成對應(yīng)的字符串。
比如:
int i = 10;#define PRINT(FORMAT, VALUE)/printf("the value of " #VALUE "is "FORMAT "/n", VALUE);...PRINT("%d", i+3);//產(chǎn)生了什么效果?
代碼中的 #VALUE 會預(yù)處理器處理為:
"VALUE" .
最終的輸出的結(jié)果應(yīng)該是:
the value of i+3 is 13
## 的作用
##可以把位于它兩邊的符號合成一個符號。
它允許宏定義從分離的文本片段創(chuàng)建標(biāo)識符
#define ADD_TO_SUM(num, value) /sum##num += value;...ADD_TO_SUM(5, 10);//作用是:給sum5增加10.
注:
這樣的連接必須產(chǎn)生一個合法的標(biāo)識符。否則其結(jié)果就是未定義的。
當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過一次的時候,如果參數(shù)帶有副作用,那么你在使用這個宏的時候就可能
出現(xiàn)危險,導(dǎo)致不可預(yù)測的后果。副作用就是表達(dá)式求值的時候出現(xiàn)的永久性效果。
例如:
x+1;//不帶副作用x++;//帶有副作用
MAX宏可以證明具有副作用的參數(shù)所引起的問題。
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )...x = 5;y = 8;z = MAX(x++, y++);printf("x=%d y=%d z=%d/n", x, y, z);//輸出的結(jié)果是什么?
這里我們得知道預(yù)處理器處理之后的結(jié)果是什么:
z = ( (x++) > (y++) ? (x++) : (y++));
所以輸出的結(jié)果是:
x=6 y=10 z=9
?宏通常被應(yīng)用于執(zhí)行簡單的運算。比如在兩個數(shù)中找出較大的一個。
?
#define MAX(a, b) ((a)>(b)?(a):(b))
那為什么不用函數(shù)來完成這個任務(wù)?
原因有二:
1. 用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實際執(zhí)行這個小型計算工作所需要的時間更多。所以宏比
函數(shù)在程序的規(guī)模和速度方面更勝一籌。
2. 更為重要的是函數(shù)的參數(shù)必須聲明為特定的類型。所以函數(shù)只能在類型合適的表達(dá)式上使用。反之
這個宏怎可以適用于整形、長整型、浮點型等可以用于>來比較的類型。宏是類型無關(guān)的。
當(dāng)然和宏相比函數(shù)也有劣勢的地方:
1. 每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序
的長度。
2. 宏是沒法調(diào)試的。
3. 宏由于類型無關(guān),也就不夠嚴(yán)謹(jǐn)。
4. 宏可能會帶來運算符優(yōu)先級的問題,導(dǎo)致程容易出現(xiàn)錯。
宏有時候可以做函數(shù)做不到的事情。比如:宏的參數(shù)可以出現(xiàn)類型,但是函數(shù)做不到。
#define MALLOC(num, type)/(type *)malloc(num * sizeof(type))...//使用MALLOC(10, int);//類型作為參數(shù)//預(yù)處理器替換之后:(int *)malloc(10 * sizeof(int));
宏和函數(shù)的一個對比
屬 性 | #define定義宏 | 函數(shù) |
代 碼 長 度 | 每次使用時,宏代碼都會被插入到程序中。除了非 常小的宏之外,程序的長度會大幅度增長 | 函數(shù)代碼只出現(xiàn)于一個地方;每 次使用這個函數(shù)時,都調(diào)用那個 地方的同一份代碼 |
執(zhí) 行 速 度 | 更快 | 存在函數(shù)的調(diào)用和返回的額外開 銷,所以相對慢一些 |
操 作 符 優(yōu) 先 級 | 宏參數(shù)的求值是在所有周圍表達(dá)式的上下文環(huán)境 里,除非加上括號,否則鄰近操作符的優(yōu)先級可能 會產(chǎn)生不可預(yù)料的后果,所以建議宏在書寫的時候 多些括號。 | 函數(shù)參數(shù)只在函數(shù)調(diào)用的時候求 值一次,它的結(jié)果值傳遞給函 數(shù)。表達(dá)式的求值結(jié)果更容易預(yù) 測。 |
帶 有 副 作 用 的 參 數(shù) | 參數(shù)可能被替換到宏體中的多個位置,所以帶有副 作用的參數(shù)求值可能會產(chǎn)生不可預(yù)料的結(jié)果。 | 函數(shù)參數(shù)只在傳參的時候求值一 次,結(jié)果更容易控制。 |
參 數(shù) 類 型 | 宏的參數(shù)與類型無關(guān),只要對參數(shù)的操作是合法 的,它就可以使用于任何參數(shù)類型。 | 函數(shù)的參數(shù)是與類型有關(guān)的,如 果參數(shù)的類型不同,就需要不同 的函數(shù),即使他們執(zhí)行的任務(wù)是 不同的。 |
調(diào) 試 | 宏是不方便調(diào)試的 | 函數(shù)是可以逐語句調(diào)試的 |
遞 歸 | 宏是不能遞歸的 | 函數(shù)是可以遞歸的 |
命名約定
一般來講函數(shù)的宏的使用語法很相似。所以語言本身沒法幫我們區(qū)分二者。
那我們平時的一個習(xí)慣是:
把宏名全部大寫
函數(shù)名不要全部大寫
這條指令用于移除一個宏定義。
#undef NAME//如果現(xiàn)存的一個名字需要被重新定義,那么它的舊名字首先要被移除。
許多C 的編譯器提供了一種能力,允許在命令行中定義符號。用于啟動編譯過程。
例如:當(dāng)我們根據(jù)同一個源文件要編譯出不同的一個程序的不同版本的時候,這個特性有點用處。(假
定某個程序中聲明了一個某個長度的數(shù)組,如果機器內(nèi)存有限,我們需要一個很小的數(shù)組,但是另外一
個機器內(nèi)存大寫,我們需要一個數(shù)組能夠大寫。)
#include int main(){int array [ARRAY_SIZE];int i = 0;for(i = 0; i< ARRAY_SIZE; i ++){array[i] = i;}for(i = 0; i< ARRAY_SIZE; i ++){printf("%d " ,array[i]);}printf("/n" );return 0;}
編譯指令:
gcc -D ARRAY_SIZE=10 programe.c
在編譯一個程序的時候我們?nèi)绻獙⒁粭l語句(一組語句)編譯或者放棄是很方便的。因為我們有條件
編譯指令。
比如說:
調(diào)試性的代碼,刪除可惜,保留又礙事,所以我們可以選擇性的編譯。
#include #define __DEBUG__int main(){int i = 0;int arr[10] = {0};for(i=0; i<10; i++){arr[i] = i;#ifdef __DEBUG__printf("%d/n", arr[i]);//為了觀察數(shù)組是否賦值成功。#endif //__DEBUG__}return 0;}
常見的條件編譯指令
?
1.#if 常量表達(dá)式//...#endif//常量表達(dá)式由預(yù)處理器求值。如:#define __DEBUG__ 1#if __DEBUG__//..#endif2.多個分支的條件編譯#if 常量表達(dá)式//...#elif 常量表達(dá)式//...#else//...#endif3.判斷是否被定義#if defined(symbol)#ifdef symbol#if !defined(symbol)#ifndef symbol4.嵌套指令#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif#endif
我們已經(jīng)知道, #include 指令可以使另外一個文件被編譯。就像它實際出現(xiàn)于 #include 指令的地方
一樣。
這種替換的方式很簡單:
預(yù)處理器先刪除這條指令,并用包含文件的內(nèi)容替換。
這樣一個源文件被包含10次,那就實際被編譯10次。
本地文件包含
#include "filename"
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)
準(zhǔn)位置查找頭文件。
如果找不到就提示編譯錯誤。
Linux環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:
/usr/include
VS環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:
C:/Program Files (x86)/Microsoft Visual Studio 12.0/VC/include
注意按照自己的安裝路徑去找。
庫文件包含
#include
查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編譯錯誤。
這樣是不是可以說,對于庫文件也可以使用 “” 的形式包含?
答案是肯定的,可以。
但是這樣做查找的效率就低些,當(dāng)然這樣也不容易區(qū)分是庫文件還是本地文件了。
如果出現(xiàn)這樣的場景:
comm.h和comm.c是公共模塊。
test1.h和test1.c使用了公共模塊。
test2.h和test2.c使用了公共模塊。
test.h和test.c使用了test1模塊和test2模塊。
這樣最終程序中就會出現(xiàn)兩份comm.h的內(nèi)容。這樣就造成了文件內(nèi)容的重復(fù)。
如何解決這個問題?
答案:條件編譯。
每個頭文件的開頭寫:
#ifndef __TEST_H__#define __TEST_H__//頭文件的內(nèi)容#endif //__TEST_H__
或者:
#pragma once
就可以避免頭文件的重復(fù)引入。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/122325.html
摘要:程序預(yù)處理本章節(jié)研究的是,源代碼文件是如何一步步得到一個可執(zhí)行程序的。如的語句被稱為預(yù)處理指令,還有注釋文本的刪除,都在此階段完成替換。目的是能夠?qū)⑺形募械拇a組合到一起成一個完整的程序。終止程序可以正常也可以意外終止程序。 ...
摘要:如的語句被稱為預(yù)處理指令,還有注釋文本的刪除,都在此階段完成替換。故宏在程序規(guī)模和執(zhí)行速度方面更勝一籌。宏替換發(fā)生在預(yù)編譯期間,故無法調(diào)試。宏可能由于運算符優(yōu)先級的問題,會導(dǎo)致程序出錯。 ...
閱讀 3256·2023-04-26 02:10
閱讀 2880·2021-10-12 10:12
閱讀 4556·2021-09-27 13:35
閱讀 1518·2019-08-30 15:55
閱讀 1057·2019-08-29 18:37
閱讀 3422·2019-08-28 17:51
閱讀 1953·2019-08-26 13:30
閱讀 1190·2019-08-26 12:09