c預處理器在源**編譯之前對其進行一些文字性質的操作。它的主要任務包括刪除注釋、插入被#include指令包含的檔案的內容、定義和替換由#define指令定義的符號以及確定**的部分內容是否應該根據一些條件編譯指令進行編譯。
在#define中,如果定義的內容很長,可以分成幾行,除了最後一行之外,每行的末尾都要加乙個反斜槓\,例如:
#define debug_print printf("file %s line %d:" \
" x=%d, y=%d, z=%d", \
__file__, __line__, \
x, y, z)
一般不在#define中定義的語句中使用分號;,而是留到實際**中使用。
#define機制包含了乙個規定,允許把引數替換到文字中,這種實現通常稱為巨集。例如:
#define name(parameter-list) stuff
其中,parameter-list是乙個由逗號分隔的符號列表,它們可能出現在stuff中。引數列表的左括號必須與name緊鄰。如果兩者之間有任何空白存在,引數列表就會被解釋為stuff的一部分。
當巨集被呼叫時,名後面是乙個由逗號分隔的值的列表,每個值都與巨集定義中的乙個引數相對應,整個列表用一對括號包圍。當引數出現在程式中時,與每個引數對應的實際值都將被替換到stuff中。例如:
#define square(x) x * x
square(5
);// 即 5*5
為巨集定義採納一種命名約定是很重要的,乙個常見的約定就是把巨集名字全部大寫。
巨集非常頻繁地用於執行簡單的計算,因為用於呼叫和從函式返回的**很可能比實際執行這個小型計算工作的**更大,另外函式引數必須指定特定的型別,而巨集可用於整型、浮點型以及其他任何可以正常使用的型別。
巨集還可以用於一些函式無法實現的功能,例如:
#define malloc(n, type) \
((type *)malloc((n) * sizeof(type)))
有些巨集還帶有***,就是在表示式求值時出現的永久性效果。例如x++。
不要在乙個巨集定義的末尾加上分號,使其成為一條完整的語句。
不要忘記在巨集定義中使用的引數的周圍加上括號。
不要忘記在巨集定義的兩邊加上括號。
#undef預處理指令用於移除乙個巨集定義:
#undef name
如果乙個現存的名字需要被重新定義,那麼它的舊定義首先必須用#undef移除。
在編譯乙個程式時,如果我們可以選擇某條語句或某組語句進行翻譯或者被忽略,常常會顯得很方便。只用於除錯程式的語句就是乙個明顯的例子。它們不應該出現在程式的產品版本中,但是你可能並不想把這些語句從源**中物理刪除,因為如果需要一些維護性修改時,你可能需要重新除錯這個程式,還需要這些語句。
條件編譯就是用於實現這個目的。使用條件編譯,你可以選擇**的一部分是被正常編譯還是完全忽略。用於支援條件編譯的基本結構是#if指令和與其匹配的#endif指令。其形式為:
#if constant-expression
statement
#endif
或者
#if constant-expression
statement
#elif constant-expression
other statement
#else
other statement
#endif
所謂常量表示式,就是說它或者是字面值常量,或者是乙個由#define定義的符號。如果變數在執行期之前無法獲得它們的值,那麼它們如果出現在常量表示式中就是非法的。例如:
#if debug
printf
("x=%d, y=%d\n"
, x, y)
;#endif
如果想編譯它時,只要使用#define debug 1即可。
測試乙個符號是否已經被定義也是可能的。在條件編譯中完成這個任務往往更為方便,因為程式如果並不需要控制編譯的符號所控制的特性,它就不需要被定義。這個測試可以通過以下任何一種方式進行:
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
每對定義的兩條語句是等價的,但#if形式的功能更強,因為常量表示式可能包含額外的條件。
還可以巢狀使用#if條件編譯,例如
#if defined(os_unix)
#ifdef option1
unix_version_of_option1()
;#endif
// option1
#ifdef option2
unix_version_of_option2()
;#endif
#elif defined(os_msdos)
#ifdef option2
msdos_version_of_option2()
;#endif
#endif
為了幫助讀者記住複雜的巢狀指令,為每個#endif加上乙個注釋標籤是很有幫助的。
當頭檔案被包含時,位於標頭檔案的所有內容都要被編譯。這個事實意味著每個標頭檔案只應該包含一組函式或資料的宣告。和把乙個程式需要的所有宣告都放入乙個巨大的標頭檔案相比,使用幾個標頭檔案,每個標頭檔案包含用於某個特定函式或模組的宣告的做法更好一些。
函式庫標頭檔案包含使用以下語法
#include
本地檔案包含使用以下語法
#include
"filename"
巢狀#include 檔案的乙個不利之處在於它使得我們很難判斷原始檔之間的真正依賴關係。另乙個不利之處在於乙個標頭檔案可能會被多次包含。
要解決這個問題,可以使用條件編譯。如果所有的標頭檔案都像下面這樣編寫:
#ifndef _headername_h
#define _headername_h 1
// 想要在標頭檔案內進行的操作
#endif
那麼多重包含的危險就被消除了。
由於這種處理將拖慢編譯速度(預處理器仍將讀入整個標頭檔案,但其內容被忽略),所以如果可能,應避免出現多重包含,不管是否由於巢狀#include檔案導致。
#progma指令是一種機制,用於支援因編譯器而異的特性。它的語法也是因編譯器而異。從本質上說,#progma是不可移植的,預處理器將忽略它不認識的#progma指令,兩個不同的編譯器可能以兩種不同的方式解釋同一條#progma指令。
#error指令在編譯時產生一條錯誤資訊,資訊中包含的是你所選擇的文字。
#line指令允許你告訴編譯器下一行輸入的行號,如果它加上了可選內容,它還將告訴編譯器輸入原始檔的名字。
《C和指標》第14章 預處理器
14.2.1 巨集 define包括乙個規定,允許把引數替換到文字中,這種實現通常稱為巨集 macro define square x x x 則程式中的square 5 會被替換成 5 5 警告 例1 a 5 printf d n square a 1 會被替換成5 1 5 1,列印結果是11,不...
C和指標(第14章 預處理器)
程式編寫,編譯第一步就是巨集的宣告,也稱預處理器階段,在編譯之前,有刪除注釋,插入被 include指令包含的檔案的內容 和 define所替換的符號等。14.1 五種預處理符號 file name.c 進行編譯的源檔名 line 25 檔案當前的行號 date jan 14 2019 檔案被編譯的...
C和指標 學習筆記 第14章 預處理器
c預處理器在源 編譯之前對其進行一些文字性質的操作。它的主要任務包括刪除注釋 插入被 include指令包含的檔案的內容 定義和替換由 define指令定義的符號以及確定 的部分內容是否應該根據一些條件編譯指令進行編譯。1.預定義符號 預處理器定義的符號,它們的值或者是字串常量,或者是十進位制數字常...