預(yù)處理器
編譯一個 C 程序設(shè)計很多步驟。其中第 1 個步驟被稱為預(yù)處理階段。C 預(yù)處理器在源代碼編譯之前對其進(jìn)行一些文本性質(zhì)的操作。他的主要任務(wù)包括刪除注釋、插入被 #include 指令包含的文件的內(nèi)容、定義、和替換由 #define 指令定義的符號以及確定代碼的部分內(nèi)容是否應(yīng)該根據(jù)一些條件編譯指令進(jìn)行編譯。
預(yù)定義符號
1.預(yù)處理器定義了一些符號,他們的值或者是字符串常量,或者是十進(jìn)制數(shù)字常量,F(xiàn)ILE的含義是進(jìn)行編譯的源文件名。
#define
宏
#define 機(jī)制包括了一個規(guī)定,允許把參數(shù)替換到文本中,這種實現(xiàn)通常稱為宏(macro)或定義宏(defined macro)。下面是宏的聲明方式:
#definename(parameter-list)stuff
其中,parameter-list(參數(shù)列表)是一個由逗號分隔的值的列表,每個值都與宏定義中的一個參數(shù)相對應(yīng),整個列表用一對括號包圍。當(dāng)參數(shù)出現(xiàn)在程序中時,與每個參數(shù)對應(yīng)的實際值都將被替換到 stuff 中。這里由一個宏。它接受一個參數(shù):
#defineSQUARE(x)((x)*(x))
如果在上述聲明之后把 SQUARE(5)置于程序中,預(yù)處理器就會用下面的這個表達(dá)式替換上面的表達(dá)式:
((5)*(5))
這里添加括號的原因是,為了避免在使用宏時,由于參數(shù)中的操作符或鄰近的操作符之間不可預(yù)料的相互作用。
#define 替換
在程序中擴(kuò)展#define定義符號和宏時,需要涉及幾個步驟。
在調(diào)用宏時,首先對參數(shù)進(jìn)行檢查,看看是否包含了任何由#define定義的符號。如果是,他們首先被替換。
替換文本隨后被插入到程序中原來文本的位置。對于宏,參數(shù)名被他們的值所替代。
最后,再次對結(jié)果文本進(jìn)行掃描,看看它是否包含了任何由#define定義的符號。如果是,就重復(fù)上述處理過程。
這樣,宏參數(shù)和#define定義可以包含其他#define定義的符號。但是,宏不可以出現(xiàn)遞歸。
#argument 替換符號
當(dāng)預(yù)處理器搜索#define定義的符號時,字符串常量的內(nèi)容并不進(jìn)行檢查。#argument 這種結(jié)構(gòu)被預(yù)處理器翻譯為 "argument",可以把一個宏參數(shù)轉(zhuǎn)化為字符串。如果想把宏參數(shù)插入到字符串常量中,可以使用下面的技巧:
技巧一
首先,臨近字符串自動連接的特性使我們很容易把一個字符串分成幾段,每段實際上都是一個宏參數(shù)。例子如下:
#definePRINT(FORMAT,VALUE) printf("Thevalueis"#FORMAT"",VALUE) ... intx=6; PRINT_FOR(%d,x+3);
輸出結(jié)果:
Thevalueis9
技巧二
#definePRINT_FOR(FORMAT,VALUE) printf("thevalueof "#VALUE"is"#FORMAT" ",VALUE) ... PRINT_FOR(%d,x+3);
輸出結(jié)果:
thevalueofx+3is9
書上的技巧一和技巧二并不能通過編譯,我經(jīng)過修改的上面的兩個例子,我感覺是用了同一個技巧就是將字符串中的 "#VALUE" 替換成傳入的字符,如技巧二中介紹的將 #VALUE替換成了字符串中的 x+3,將 #FORMAT 替換成 %d,最后一個 VALUE 作為實際的參數(shù)只是進(jìn)行了簡單的文本替換,不涉及字符串修改。
技巧三
#definePRINT(VALUE)printf(#VALUE) ... PRINT(helloSummerGift);
輸出結(jié)果:
helloSummerGift
技巧三展示了基本的 #argument功能,即將 #VALUE替換成 "hello SummerGift",也就是將 hello SummerGift 簡單替換成了 "hello SummerGift"。
##連接符號
##結(jié)構(gòu)把位于它兩邊的符號連接成一個符號。作為用途之一,它允許宏定義從分離的文本片段創(chuàng)建標(biāo)識符。下面的這個例子使用這種連接把一個值添加到幾個變量之一:
#defineADD_TO_SUM(sum_number,value) sum##sum_number+=value ... ADD_TO_SUM(5,25);
最后一條語句把值25加到變量 sum5。注意這種連接必須產(chǎn)生一個合法的標(biāo)識符。否則其結(jié)果就是未定義的。
宏與函數(shù)
用宏來完成比較簡單的計算,比如比較兩個表達(dá)式的大小的好處是,如果使用函數(shù)來實現(xiàn),用于調(diào)用和從函數(shù)返回的代碼很可能比實際執(zhí)行這個小型計算任務(wù)的代碼更大,所以使用宏比使用函數(shù)在程序的規(guī)模和速度方面都更勝一籌。#define MAX(A, B) ((A)>(B) ? (A):(B))
更為重要的是,函數(shù)的參數(shù)必須聲明為一種特定的類型,所以它只能在類型合適的表達(dá)式上使用。反之,上面這個宏可以用于整形、長整形、單浮點型、雙浮點數(shù)以及其他任何可以用 > 操作符比較值大小的類型。換句話說,宏是與類型無關(guān)的。
與函數(shù)相比,使用宏的不利之處在于每次使用宏時,一份宏定義代碼的拷貝都將插入到程序中。除非宏非常短,否則使用宏可能會大幅度增加程序的長度。
還有一些任務(wù)根本無法用函數(shù)實現(xiàn)。比如下面的宏定義,這個宏的第二個參數(shù)是一種類型,它無法作為函數(shù)參數(shù)進(jìn)行傳遞。
#defineMALLOC(n,type) ((type*)malloc((n)*sizeof(type)))
實際轉(zhuǎn)換過程如下:
轉(zhuǎn)換前:pi = MALLOC(25, int); 轉(zhuǎn)換后:pi =((int *)malloc((25)* sizeof( int )))
帶副作用的宏參數(shù)
當(dāng)宏參數(shù)在宏定義中出現(xiàn)的次數(shù)超過一次時,如果這個參數(shù)具有副作用,那么當(dāng)你使用這個宏時就出現(xiàn)危險導(dǎo)致不可預(yù)料的結(jié)果。副作用就是在表達(dá)式求值時出現(xiàn)的永久性結(jié)果,例如:
x+1
這個表達(dá)式可以重復(fù)執(zhí)行幾百次,他每次獲得的值結(jié)果都是一樣的。這個表達(dá)式不具有副作用。但是
x++
就具有副作用:它增加 x 的值。當(dāng)這個表達(dá)式下一次執(zhí)行時,它將產(chǎn)生一個不同的結(jié)果。MAX宏可以證明具有副作用的參數(shù)引起的問題。
#defineMAX(A,B)((A)>(B)?(A):(B)) ... x=5; y=8; z=MAX(x,y); printf("x=%d,y=%d,z=%d",x,y,z);
這個宏的結(jié)果是 x = 6, y = 10, z = 9。 檢查替換后的結(jié)果:z =(x++)>(y++) ? (x++):(y++);
那個較小的值只增值了一次,而那個較大的值卻增值了兩次,第一次是在比較的時候,第二次是在執(zhí)行 ?后面的表達(dá)式的時候。
命名約定
為宏采納一種命名約定是很重要的,用來區(qū)分一個名稱是一個函數(shù)還是一個宏,避免發(fā)生混淆。
#undef
這條預(yù)處理指令用于移除一個宏定義。
#undefname
如果一個現(xiàn)存的名字需要被重新定義,那么他的舊定義首先必須用#undef移除。如果一個宏不想讓后面的程序用了,那么就將其 undef ,如果后面使用了這個宏就會報錯。
命令行定義
許多編譯器提供了一種能力,允許在命令行中定義符號,用于啟動編譯過程。當(dāng)我們根據(jù)同一個源文件編譯一個程序的不同版本時,這個特性是很有用的。例如有如下數(shù)組:
intarray[ARRAY_SIZE];
那么在UNIX系統(tǒng)中,編譯這個程序的命令行很可能是下面的樣子:
cc-DARRY_SIZE=100prog.c
條件編譯
常見的條件編譯:
#ifconstant-expression statments #endif
其中,constant-expression(常量表達(dá)式)由預(yù)處理器進(jìn)行求值,如果其值為真,則statements部分就被正常編譯,否則預(yù)處理器就安靜地刪除他們。
是否被定義
測試一個符號是否被定義也是可能的。在條件編譯中完成這個任務(wù)往往更方便,因為程序如果并不需要控制編譯的符號所控制的特性,他就不需要被定義。這個測試可以用下面的方式來進(jìn)行:
#ifdefined(symbol) #ifdefsymbol #if!defined(symbol) #ifndefsymbol
每對定義的兩條語句是等價的,但#if形式功能更強(qiáng)。因為常量表達(dá)式可能包含額外的條件,如下面所示:
#ifx>0||defined(ABD)&&defined(BCD)
嵌套指令
上面提到的這些指令可以嵌套于另一個指令內(nèi)部。
#ifxxx statments #elifxxx statments #endif
文件包含
使用#include指令的替換執(zhí)行方式很簡單,預(yù)處理刪除這條指令,并用包含文件的內(nèi)容取而代之。這樣一個頭文件如果被包含到10個源文件中,它實際被編譯了10次。
編譯器支持兩種不同類型的 #include文件包含:函數(shù)庫文件和本地文件。實際上,他們之間的區(qū)別很小。
函數(shù)庫文件包含
函數(shù)庫頭文件包含使用下面的語法:
#include
這種情況的包含會在系統(tǒng)目錄里尋找函數(shù)庫頭文件。也可以使用編譯器選項添加自己的文件函數(shù)庫。
本地文件包含
本地頭文件包含使用下面的語法:
#include"filename"
這種情況的包含會先在當(dāng)前目錄進(jìn)行查找,如果未找到再像查找函數(shù)庫頭文件一樣在標(biāo)準(zhǔn)位置查找本地頭文件。嵌套文件包含 使用條件編譯來避免重復(fù)包含頭文件出現(xiàn)的錯誤:
#ifndef__HEADERNAME__H #define__HEADERNAME__H1 /* **Allthestuffthatyouwangintheheaderfile */ #endif
其他指令
#error指令允許生成錯誤信息 #errortextoferrormessage
使用方法:
#errornooptionselsected #line修改下一行輸入的行號 #linenumber"string"
它通知預(yù)處理器 number 使下一行輸入的行號。如果給出了可選部分 string ,預(yù)處理器就把他當(dāng)作當(dāng)前文件的名字。值得注意的是,這條指令修改__LINE__符號的值,如果加上可選部分,他還將修改__FILE__符號的值。
#progma用于支持因編譯器而異的特性
#progma指令是另一種機(jī)制,用于支持因編譯器而異的機(jī)制。它的語法也是因編譯器而異。有些環(huán)境可能提供一些#progma指令,允許一些編譯選項或者其他任何任何方式無法實現(xiàn)的一些處理方式。例如有些編譯器使用progma指令在編譯過程中打開或者關(guān)閉清單顯示,或者把會變代碼插入到 C 程序中。從本質(zhì)上說,#progma是不可移植的。預(yù)處理器將忽略他們不認(rèn)識的#progma指令,兩個不同的編譯器可能以兩種不同的方式解釋同一條#progma指令。
#(null directive) 無效指令
無效指令就是一個#開頭,但后面不跟任何內(nèi)容的一行。這類指令只是被預(yù)處理器簡單地刪除。下面的例子中無效指令通過把#include與周圍的代碼分隔開來,凸顯它的存在。
# #include#
插入空行也可以取得相同的效果。
總結(jié)
警告的總結(jié)
不要在一個宏定義的末尾加上分號,使其成為一條完整的語句。
在宏定義中使用參數(shù),但忘了在他們周圍加上括號。
忘了在整個宏定義的兩邊加上括號。
編程提示的總結(jié)
避免用 #define 指令定義可以用函數(shù)實現(xiàn)的很長序列的代碼。
在那些對表達(dá)式求值的宏中,每個宏參數(shù)出現(xiàn)的地方都應(yīng)該加上括號,并且在整個宏定義的兩邊也加上括號。
避免使用 #define 宏創(chuàng)建一種新的語言。
采用采用命名約定,使程序員很容易看出某個標(biāo)識符是否為 #define 宏。
只要合適就應(yīng)該使用文件包含,不必?fù)?dān)心它的額外開銷。
頭文件只應(yīng)該包含一組函數(shù)和(或)數(shù)據(jù)的聲明。
把不同集合的聲明分離到不同的頭文件中可以改善信息隱藏。
嵌套的 #include 文件是我們很難判斷源文件之間的依賴關(guān)系。
審核編輯:湯梓紅
-
指令
+關(guān)注
關(guān)注
1文章
604瀏覽量
35581 -
字符串
+關(guān)注
關(guān)注
1文章
567瀏覽量
20432 -
編譯
+關(guān)注
關(guān)注
0文章
646瀏覽量
32741 -
C程序
+關(guān)注
關(guān)注
4文章
254瀏覽量
35945 -
預(yù)處理器
+關(guān)注
關(guān)注
0文章
13瀏覽量
2215
原文標(biāo)題:預(yù)處理相關(guān)知識點總結(jié)
文章出處:【微信號:嵌入式應(yīng)用研究院,微信公眾號:嵌入式應(yīng)用研究院】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論