預處理器
編譯一個 C 程序設計很多步驟。其中第 1 個步驟被稱為預處理階段。C 預處理器在源代碼編譯之前對其進行一些文本性質的操作。他的主要任務包括刪除注釋、插入被 #include 指令包含的文件的內容、定義、和替換由 #define 指令定義的符號以及確定代碼的部分內容是否應該根據一些條件編譯指令進行編譯。
預定義符號
1.預處理器定義了一些符號,他們的值或者是字符串常量,或者是十進制數字常量,FILE的含義是進行編譯的源文件名。

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