本文主要講述C/C++中預(yù)處理命令相關(guān)的內(nèi)容。主要參考資料在后面給出。
我們可以在C源程序中插入傳給編譯程序的各種指令(宏),這些指令被稱為預(yù)處理器指令,它們擴(kuò)充了程序設(shè)計(jì)的環(huán)境。
在將一個(gè)C源程序轉(zhuǎn)換為可執(zhí)行程序的過(guò)程中, 編譯預(yù)處理是最初的步驟. 這一步驟是由預(yù)處理器(preprocessor)來(lái)完成的. 在源程序被編譯器處理之前, 預(yù)處理器首先對(duì)源程序中的"宏(macro)"進(jìn)行處理.
C 初學(xué)者可能對(duì)預(yù)處理器沒(méi)什么概念, 這是情有可原的: 一般的C編譯器都將預(yù)處理, 匯編, 編譯, 連接過(guò)程集成到一起了,編譯預(yù)處理往往在后臺(tái)運(yùn)行。 在有的C編譯器中, 這些過(guò)程統(tǒng)統(tǒng)由一個(gè)單獨(dú)的程序來(lái)完成, 在編譯的不同階段分別實(shí)現(xiàn)這些不同的功能,我們可以指定特定的命令選項(xiàng)來(lái)執(zhí)行這些指定功能。有的C編譯器則使用獨(dú)立的程序來(lái)完成這些步驟. 可單獨(dú)調(diào)用這些程序來(lái)完成特定步驟,比如在 gcc
中, 進(jìn)行編譯預(yù)處理的程序被稱為 CPP
, 它的可執(zhí)行文件名為 cpp
。
編譯預(yù)處理命令的語(yǔ)法與C語(yǔ)言的語(yǔ)法是完全獨(dú)立的,比如: 你可以將一個(gè)宏擴(kuò)展為與C語(yǔ)法格格不入的內(nèi)容, 但該內(nèi)容與后面的語(yǔ)句結(jié)合在一個(gè)若能生成合法的C語(yǔ)句, 也是可以正確編譯的。
預(yù)處理命令簡(jiǎn)介
預(yù)處理命令由#(hash字符)開(kāi)頭, 它獨(dú)占一行, #之前只能是空白符. 以#開(kāi)頭的語(yǔ)句就是預(yù)處理命令, 不以#開(kāi)頭的語(yǔ)句為C中的代碼行。
常用的預(yù)處理命令如下:
#define 定義一個(gè)預(yù)處理宏
#undef 取消宏的定義
#if 編譯預(yù)處理中的條件命令, 相當(dāng)于C語(yǔ)法中的if語(yǔ)句
#ifdef 判斷某個(gè)宏是否被定義, 若已定義, 執(zhí)行隨后的語(yǔ)句
#ifndef 與#ifdef相反, 判斷某個(gè)宏是否未被定義
#elif 若#if, #ifdef, #ifndef或前面的#elif條件不滿足, 則執(zhí)行#elif之后的語(yǔ)句, 相當(dāng)于C語(yǔ)法中的else-if
#else 與#if, #ifdef, #ifndef對(duì)應(yīng), 若這些條件不滿足, 則執(zhí)行#else之后的語(yǔ)句, 相當(dāng)于C語(yǔ)法中的else
#endif #if, #ifdef, #ifndef這些條件命令的結(jié)束標(biāo)志.
defined 與#if, #elif配合使用, 判斷某個(gè)宏是否被定義
#include 包含文件命令
#include_next 與#include相似, 但它有著特殊的用途
#line 標(biāo)志該語(yǔ)句所在的行號(hào)
# 將宏參數(shù)替代為以參數(shù)值為內(nèi)容的字符竄常量
## 將兩個(gè)相鄰的標(biāo)記(token)連接為一個(gè)單獨(dú)的標(biāo)記
#pragma 說(shuō)明編譯器信息
#warning 顯示編譯警告信息
#error 顯示編譯錯(cuò)誤信息
預(yù)處理的文法
預(yù)處理并不分析整個(gè)源代碼文件, 它只是將源代碼分割成一些標(biāo)記(token), 識(shí)別語(yǔ)句中哪些是C語(yǔ)句, 哪些是預(yù)處理語(yǔ)句。
預(yù)處理器能夠識(shí)別C標(biāo)記, 文件名, 空白符, 文件結(jié)尾標(biāo)志。
預(yù)處理語(yǔ)句格式: #command name(...) token(s)
這里,
-
command
預(yù)處理命令的名稱。它之前以
#
開(kāi)頭, #之后緊隨預(yù)處理命令, 標(biāo)準(zhǔn)C允許#
兩邊可以有空白符, 但比較老的編譯器可能不允許這樣. 若某行中只包含#
(以及空白符), 那么在標(biāo)準(zhǔn)C中該行被理解為空白. 整個(gè)預(yù)處理語(yǔ)句之后只能有空白符或者注釋, 不能有其它內(nèi)容。 -
name
代表宏名稱,它可帶參數(shù)。參數(shù)可以是可變參數(shù)列表(C99).
-
token(s)
宏體,將替換宏名稱的語(yǔ)句。語(yǔ)句中可以利用
\
來(lái)?yè)Q行.
e.g.
# define ONE 1 /* ONE == 1 */
等價(jià)于:
#define ONE 1
#define err(flag, msg) if(flag) \
printf(msg)
等價(jià)于:
#define err(flag, msg) if(flag) printf(msg)
預(yù)處理命令詳述
1、聲明定義
#define
#define
命令定義一個(gè)宏,定義一個(gè)標(biāo)識(shí)符和一個(gè)串(也就是字符集),在源程序中發(fā)現(xiàn)該標(biāo)識(shí)符時(shí),都用該串替換之。這種標(biāo)識(shí)符稱為宏名字,相應(yīng)的替換稱為宏代換。
一般形式如下:
#define MACRO_NAME(args) tokens(opt)
這種語(yǔ)句不用分號(hào)結(jié)尾。宏名字和串之間可以有多個(gè)空白符,但串開(kāi)始后只能以新行終止。之后出現(xiàn)的 MACRO_NAME
將被替代為所定義的標(biāo)記( tokens
). 宏可帶參數(shù), 而后面的標(biāo)記也是可選的。
對(duì)象宏
不帶參數(shù)的宏被稱為"對(duì)象宏(objectlike macro)"。
#define
經(jīng)常用來(lái)定義常量, 此時(shí)的宏名稱一般為大寫(xiě)的字符串。 這樣利于修改這些常量。
e.g.
#define MAX 100
int a[MAX];
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
#endif
這里, #define __FILE_H__
中的宏就不帶任何參數(shù), 也不擴(kuò)展為任何標(biāo)記. 這經(jīng)常用于包含頭文件.
要調(diào)用宏, 只需在代碼中指定宏名稱, 相應(yīng)宏將被替代為它被定義的內(nèi)容。
例如:我們使用 LEFT
代表1,用 RIGHT
代表0,我們使用兩個(gè) #define
指令:
#define LEFT 1
#define RIGHT 0
每當(dāng)在源程序中遇到 LEFT
或 RIGHT
時(shí),編譯程序都用1或0替換。
宏代換就是用相關(guān)的串替代標(biāo)識(shí)符。因此,如果希望定義一條標(biāo)準(zhǔn)錯(cuò)誤信息時(shí),可以如下定義:
#define ERROR_MS “Standard error on input \n”
如果一個(gè)串長(zhǎng)于一行,可在行尾用反斜線”\”續(xù)行,如下:
#define LONG_STRING “This is a very very long \
String that is used as an example”
函數(shù)宏
帶參數(shù)的宏也被稱為"函數(shù)宏". 利用宏可以提高代碼的運(yùn)行效率: 子程序的調(diào)用需要壓棧出棧, 這一過(guò)程如果過(guò)于頻繁會(huì)耗費(fèi)掉大量的CPU運(yùn)算資源. 所以一些代碼量小但運(yùn)行頻繁的代碼如果采用帶參數(shù)宏來(lái)實(shí)現(xiàn)會(huì)提高代碼的運(yùn)行效率.
-
函數(shù)宏的參數(shù)是固定的情況
函數(shù)宏的定義采用這樣的方式:
#define name( args ) tokens
其中的
args
和tokens
都是可選的. 它和對(duì)象宏定義上的區(qū)別在于對(duì)象宏名稱之后不帶括號(hào).注意: 定義時(shí) ,
name
之后的左括號(hào)(
必須緊跟name
, 之間不能有空格, 否則這就定義了一個(gè)對(duì)象宏, 它將被替換為以(
開(kāi)始的字符串;但在 用函數(shù)宏時(shí) ,name
與(
之間卻可以有空格.例如:
#define mul(x,y) ((x)*(y))
注意, 函數(shù)宏之后的參數(shù)要用括號(hào)括起來(lái), 看看這個(gè)例子:
#define mul(x,y) x*y
mul(1, 2+2);
將被擴(kuò)展為:1*2 + 2
同樣, 整個(gè)標(biāo)記串也應(yīng)該用括號(hào)引用起來(lái):
#define mul(x,y) (x)*(y)
sizeof mul(1,2.0)
將被擴(kuò)展為sizeof 1 * 2.0
調(diào)用函數(shù)宏時(shí)候, 傳遞給它的參數(shù)可以是函數(shù)的返回值, 也可以是任何有意義的語(yǔ)句:
e.g.mul (f(a,b), g(c,d));
e.g.
#define insert(stmt) stmt
這里:
- insert ( a=1; b=2;) 相當(dāng)于在代碼中加入 a=1; b=2 .
- insert ( a=1, b=2;) 就有問(wèn)題了: 預(yù)處理器會(huì)提示出錯(cuò): 函數(shù)宏的參數(shù)個(gè)數(shù)不匹配. 預(yù)處理器把","視為參數(shù)間的分隔符.
- insert ((a=1, b=2;)) 可解決上述問(wèn)題.
-
在定義和調(diào)用函數(shù)宏時(shí)候, 要注意一些問(wèn)題
-
使用
do-while(0)
解決結(jié)尾;
問(wèn)題我們經(jīng)常用
{}
來(lái)引用函數(shù)宏被定義的內(nèi)容, 這就要注意調(diào)用這個(gè)函數(shù)宏時(shí)的;
問(wèn)題.
example_3.7:#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}
如果這樣調(diào)用它:
swap(1,2);
將被擴(kuò)展為:{ unsigned long _temp=1; 1=2; 2=_tmp};
明顯后面的;是多余的, 我們應(yīng)該這樣調(diào)用:
swap(1,2)
雖然這樣的調(diào)用是正確的, 但它和C語(yǔ)法相悖, 可采用下面的方法來(lái)處理被
{}
括起來(lái)的內(nèi)容:#define swap(x,y) \ do { unsigned long _temp=x; x=y; y=_tmp} while (0)
swap(1,2);
將被替換為:do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);
在Linux內(nèi)核源代碼中對(duì)這種
do-while(0)
語(yǔ)句有這廣泛的應(yīng)用. -
無(wú)法被
do-while(0)
實(shí)現(xiàn)的宏有的函數(shù)宏是無(wú)法用
do-while(0)
來(lái)實(shí)現(xiàn)的, 所以在調(diào)用時(shí)不能帶上;
, 最好在調(diào)用后添加注釋說(shuō)明。
eg_3.8:#define incr(v, low, high) \ for ((v) = (low),; (v) <= (high); (v)++)
只能以這樣的形式被調(diào)用:
incr(a, 1, 10) /* increase a form 1 to 10 */
-
-
函數(shù)宏中的參數(shù)包括可變參數(shù)列表的情況
C99標(biāo)準(zhǔn)中新增了可變參數(shù)列表的內(nèi)容。 不光是函數(shù), 函數(shù)宏中也可以使用可變參數(shù)列表。
#define name(args, ...) tokens #define name(...) tokens
...
代表可變參數(shù)列表, 如果它不是僅有的參數(shù), 那么它只能出現(xiàn)在參數(shù)列表的最后. 調(diào)用這樣的函數(shù)宏時(shí), 傳遞給它的參數(shù)個(gè)數(shù)要不少于參數(shù)列表中參數(shù)的個(gè)數(shù)(多余的參數(shù)被丟棄)。通過(guò)
__VA_ARGS__
來(lái)替換函數(shù)宏中的可變參數(shù)列表. 注意__VA_ARGS__
只能用于函數(shù)宏中參數(shù)中包含有...
的情況.e.g.
#ifdef DEBUG #define my_printf(...) fprintf(stderr, __VA_ARGS__) #else #define my_printf(...) printf(__VA_ARGS__) #endif
tokens
中的__VA_ARGS__
被替換為函數(shù)宏定義中的...
可變參數(shù)列表。
注意在使用#define時(shí)候的一些常見(jiàn)錯(cuò)誤
#define MAX = 100
#define MAX 100;
=, ; 的使用要值得注意,再就是調(diào)用函數(shù)宏是要注意, 不要多給出 ;
。
注意: 函數(shù)宏對(duì)參數(shù)類型是不敏感的, 你不必考慮將何種數(shù)據(jù)類型傳遞給宏。 那么, 如何構(gòu)建對(duì)參數(shù)類型敏感的宏呢? 參考本章的第九部分, 關(guān)于 ##
的介紹。
關(guān)于定義宏的另外一些問(wèn)題
-
(1)重復(fù)定義宏
宏可以被多次定義, 前提是這些定義必須是相同的. 這里的"相同"要求 先后定義中空白符出現(xiàn)的位置相同, 但具體的空白符類型或數(shù)量可不同, 比如原先的空格可替換為多個(gè)其他類型的空白符: 可為tab, 注釋…
e.g.
#define NULL 0 #define NULL /* null pointer */ 0
上面的重定義是相同的, 但下面的重定義不同:
#define fun(x) x+1 #define fun(x) x + 1 //或: #define fun(y) y+1
如果多次定義時(shí), 再次定義的宏內(nèi)容是不同的,
gcc
會(huì)給出NAME redefined
警告信息。應(yīng)該避免重新定義函數(shù)宏, 不管是在預(yù)處理命令中還是C語(yǔ)句中, 最好對(duì)某個(gè)對(duì)象只有單一的定義. 在
gcc
中, 若宏出現(xiàn)了重定義,gcc
會(huì)給出警告。 -
(2) 命令行擴(kuò)展定義
在
gcc
中, 可在命令行中指定對(duì)象宏的定義:e.g.
$gcc -Wall -DMAX=100 -o tmp tmp.c
相當(dāng)于在
tmp.c
中添加#define MAX 100
。那么, 如果原先
tmp.c
中含有MAX宏的定義, 那么再在gcc
調(diào)用命令中使用-DMAX
, 會(huì)出現(xiàn)什么情況呢?- 若
-DMAX=1
, 則正確編譯. - 若
-DMAX
的值被指定為不為1的值, 那么gcc
會(huì)給出MAX
宏被重定義的警告,MAX
的值仍為1.
注意: 若在調(diào)用
gcc
的命令行中不顯示地給出對(duì)象宏的值, 那么gcc
賦予該宏默認(rèn)值(1), 如: -DVAL == -DVAL=1 - 若
-
(3) 宏定義作用域
#define
所定義的宏的作用域
宏在定義之后才生效, 若宏定義被#undef
取消, 則#undef
之后該宏無(wú)效. 并且字符串中的宏不會(huì)被識(shí)別e.g.
#define ONE 1 sum = ONE + TWO /* sum = 1 + TWO */ #define TWO 2 sum = ONE + TWO /* sum = 1 + 2 */ #undef ONE sum = ONE + TWO /* sum = ONE + 2 */ char c[] = "TWO" /* c[] = "TWO", NOT "2"! */
-
(4) 遞歸嵌套定義
宏的替換可以是遞歸的, 所以可以嵌套定義宏。也就是說(shuō),定義一個(gè)宏名字之后,可以在其他宏定義中使用。
e.g.
# define ONE NUMBER_1 # define NUMBER_1 1 int a = ONE /* a = 1 */ #define ONE 1 #define TWO ONE+ONE #define THREE ONE+TWO
#undef
#undef
用來(lái)取消宏定義, 它與 #define
對(duì)立,經(jīng)常用來(lái)刪除前面定義的宏名字。也就是說(shuō),它“不定義”宏。一般形式為:
#undef macro-name
如夠被取消的宏實(shí)際上沒(méi)有被 #define
所定義, 針對(duì)它的 #undef
并不會(huì)產(chǎn)生錯(cuò)誤,當(dāng)一個(gè)宏定義被取消后, 可以再度定義它。
#, ##
預(yù)處理操作符 #
和 ##
主要作用是允許預(yù)處理程序?qū)Ω赌承┨厥馇闆r,多數(shù)程序中并不需要,它們可以在 #define
中使用。
由于經(jīng)常用于對(duì)字符串的預(yù)處理操作, 所以他們也經(jīng)常用于 printf, puts
之類的字符串顯示函數(shù)中。例如:
e.g.
#define TEST(a,b) printf( #a "<" #b "=%d\n", (a)<(b));
#
命令
操作符 #
通常稱為字符串化的操作符,它把其后的串變成用雙引號(hào)包圍的串。用于在宏擴(kuò)展之后將 tokens
轉(zhuǎn)換為以 tokens
為內(nèi)容的字符串常量。
例如:
#include <stdio.h>
#define mkstr(s) #s
int main(void)
{
printf(mkstr(I like C));
Return 0;
}
預(yù)處理程序把以下的語(yǔ)句: printf(mkstr(I like C));
變成 printf(“I like C”);
注意: #
只針對(duì)緊隨其后的 token
有效!
##
命令
操作符 ##
把兩個(gè)標(biāo)記拼在一起,形成一個(gè)新標(biāo)記。用于將它前后的兩個(gè) token
組合在一起轉(zhuǎn)換成以這兩個(gè) token
為內(nèi)容的字符串常量。
#include <stdio.h>
#define concat(a,a) a##b
int main(void)
{
int xy = 10;
printf(“%d”,concat(x,y));
Return 0;
}
預(yù)處理程序把以下語(yǔ)句: printf(“%d”,concat(x,y));
變成 printf(“%d”,xy);
注意: ##
前后必須要有 token
!
2、條件編譯
若干編譯指令允許程序員有選擇的編譯程序源代碼的不同部分,這種過(guò)程稱為條件編譯。
#if, #elif, #else, #endif
#if, #elif, #else, #endif
允許程序員根據(jù)常數(shù)表達(dá)式的結(jié)果有條件的包圍部分代碼。一般的表達(dá)形式是:
#if 常量表達(dá)式1
語(yǔ)句...
#elif 常量表達(dá)式2
語(yǔ)句...
#elif 常量表達(dá)式3
語(yǔ)句...
...
#else
語(yǔ)句...
#endif
它們后面所判斷的宏只能是對(duì)象宏. 如果 name
為名的宏未定義, 或者該宏是函數(shù)宏. 那么在 gcc
中使用 -Wundef
選項(xiàng)會(huì)顯示宏未定義的警告信息。
#if
表示“如果”, 其一般形式是:
#if constant-expression
Statement sequence
#endif
如 #if
后的常數(shù)表達(dá)式為真,則 #if
和 #endif
中間的代碼被編譯,否則忽略該代碼段。 #endif
標(biāo)記 #if
塊的結(jié)束。
#elif
指令
表示“否則,如果”,為多重編譯選擇建立一條 if-else-if
(如果-否則-如果鏈)。一般形式如下:
#if expression
Statement sequence
#elif expression1
Statement sequence
#elif expression2
Statement sequence
#elif expression
Statement sequence
#endif
如果 #if
表達(dá)式為真,該代碼塊被編譯,不測(cè)試其他 #elif
表達(dá)式。否則,序列中的下一塊被測(cè)試,如果成功則編譯之。
#else
指令
作用與C語(yǔ)言的 else
相似, #if
指令失敗時(shí)它可以作為備選指令。例如:
#include <stdio.h>
#define MAX 100
int main(void)
{
#if MAX>99
printf(“Compiled for array greater than 99.\n”);
#else
printf(“Complied for small array.\n”);
#endif
return 0;
}
注意, #else
既是標(biāo)記 #if
塊的結(jié)束,也標(biāo)記 #else
塊的開(kāi)始。因?yàn)槊總€(gè) #if
只能寫(xiě)一個(gè) #endif
匹配。
與代碼中的 if/else
類似
#if
和 #else
分別相當(dāng)于C語(yǔ)句中的 if
, else
。它們根據(jù)常量表達(dá)式的值來(lái)判別是否執(zhí)行后面的語(yǔ)句。 #elif
相當(dāng)于C中的 else-if
。使用這些條件編譯命令可以方便地實(shí)現(xiàn)對(duì)源代碼內(nèi)容的控制。 else
之后不帶常量表達(dá)式, 但若包含了常量表達(dá)式, gcc
只是給出警告信息。
使用它們可以提升代碼的可移植性——針對(duì)不同的平臺(tái)使用執(zhí)行不同的語(yǔ)句. 也經(jīng)常用于大段代碼注釋。
e.g.
#if 0
{
一大段代碼;
}
#endif
常量表達(dá)式可以是包含宏, 算術(shù)運(yùn)算, 邏輯運(yùn)算等等的合法C常量表達(dá)式, 如果常量表達(dá)式為一個(gè)未定義的宏, 那么它的值被視為0,
#if MACRO_NON_DEFINED == #if 0
在判斷某個(gè)宏是否被定義時(shí), 應(yīng)當(dāng)避免使用#if, 因?yàn)樵摵甑闹悼赡芫褪潜欢x為0。 而應(yīng)當(dāng)使用下面介紹的 #ifdef
或 #ifndef
。
#ifdef, #ifndef, defined
#ifdef, #ifndef, defined
用來(lái)測(cè)試某個(gè)宏是否被定義。
與 #if, #elif, #else
不同, #indef, #ifndef, defined
測(cè)試的宏可以是對(duì)象宏, 也可以是函數(shù)宏。
在 gcc
中使用 -Wundef
選項(xiàng)不會(huì)顯示宏未定義的警告信息。
#ifdef
指令
一般形式如下:
#ifdef macro-name
Statement sequence
#endif
如果 macro-name
原先已經(jīng)被一個(gè) #define
語(yǔ)句定義,則編譯其中的代碼塊。
#ifndef
指令
一般形式如下:
#ifndef macro-name
Statement sequence
#endif
如果 macro-name
當(dāng)前未被 #define
語(yǔ)句定義,則編譯其中的代碼塊。
#ifndef
經(jīng)常用于避免頭文件的重復(fù)引用,例如:
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
#endif
defined
指令
除 #ifdef
之外,還有另外一種確定是否定義宏名字的方法,即可以將 #if
指令與 defined
指令一起使用。 defined
操作符的一般形式如下:
defined macro-name
若 macro-name
當(dāng)前被定義,則表達(dá)式為真返回1,否則為假返回0。
defined
與 #if, #elif, #else
結(jié)合使用來(lái)判斷宏是否被定義, 乍一看好像它顯得多余, 因?yàn)橐呀?jīng)有了 #ifdef
和 #ifndef
,其實(shí)使用 defined
的一個(gè)原因是,它允許被做為 #ifdef
與 #ifndef
, #elif
的判斷條件(如:由 #elif
語(yǔ)句確定宏名字存在),因此顯得更為靈活。
-
與
#ifdef
等價(jià)的判斷例如,確定宏
MY
是否定義,可以使用下列兩種預(yù)處理命令之一:#if defined MY
或
#ifdef MY
-
與
#ifndef
等價(jià)的判斷也可以在
defined
之前加上感嘆號(hào)”!”來(lái)反轉(zhuǎn)相應(yīng)的條件。例如,只有在DEBUG未定義的情況下才編譯。#if !defined DEBUG printf(“Final Version!\n”); #endif
或
#ifndef DEBUG printf(“Final Version!\n”); #endif
-
defined
做為條件語(yǔ)句的判斷比如在一條判斷語(yǔ)句中聲明多個(gè)判別條件,如:
#if defined(VAX) && defined(UNIX) && !defined(DEBUG)
注:可以沒(méi)有括號(hào)。
結(jié)合 #else
/ #elif
指令使用
#ifdef
/ #ifndef
可與 #else
/ #elif
結(jié)合使用,如下:
#inlucde <stdio.h>
#define T 10
int main(void)
{
#ifdef t
printf(“Hi T\n”);
#else
printf(“Hi anyone\n”);
#endif
#ifndef M
printf(“M Not Defined\n”);
#endif
Return 0;
}
3、文件包含
#include
指令
用于文件包含. 含義是要求編譯程序讀入另一個(gè)源文件。在 #include
命令所在的行不能含有除注釋和空白符之外的其他任何內(nèi)容。一般形式:
#include "headfile"
#include <headfile>
#include 預(yù)處理標(biāo)記
前面兩種形式大家都很熟悉,被讀入文件的名字必須用雙引號(hào)(“”
)或一對(duì)尖括號(hào)(<>
)包圍,例如:
#include “stdio.h”
#include <stdio.h>
表示都使C編譯程序讀入并編譯頭文件以用于I/O系統(tǒng)庫(kù)函數(shù)。
在 #include 預(yù)處理標(biāo)記
形式中, 預(yù)處理標(biāo)記會(huì)被預(yù)處理器進(jìn)行替換, 替換的結(jié)果必須符合前兩種形式中的某一種。
包含文件中可以包含其他 #include
指令,稱為嵌套包含。允許的最大嵌套深度隨編譯器而變。
被包含文件名用雙引號(hào)或尖括號(hào)包圍決定了對(duì)指定文件的搜索方式。當(dāng)文件名被尖括號(hào)包圍時(shí),搜索按編譯程序(如 gcc
)作者的定義進(jìn)行,一般用于搜索某些專門(mén)放置包含文件的特殊目錄。當(dāng)文件名被雙引號(hào)包圍時(shí),搜索按編譯程序?qū)崟r(shí)的規(guī)定進(jìn)行,一般搜索當(dāng)前目錄。如未發(fā)現(xiàn),再按尖括號(hào)包圍時(shí)的辦法重新搜索一次。通常,絕大多數(shù)程序員使用尖括號(hào)包圍標(biāo)準(zhǔn)的頭文件,雙引號(hào)用于包圍與當(dāng)前程序相關(guān)的文件名。
實(shí)際上, 真正被添加的頭文件并不一定就是 #include
中所指定的文件。 #include "headfile"= 包含的頭文件當(dāng)然是同一個(gè)文件, 但 =#include <headfile>
包包含的"系統(tǒng)頭文件"可能是另外的文件. 但這不值得被注意. 感興趣的話可以查看宏擴(kuò)展后到底引入了哪些系統(tǒng)頭文件。
關(guān)于 #include "headfile"和#include <headfile>
的區(qū)別以及如何在 gcc
中包含頭文件的詳細(xì)信息, 具體需要參考GCC相關(guān)的文檔。
#include_next
指令
相對(duì)于 #include
, 我們對(duì) #include_next
不太熟悉。 #include_next
僅用于特殊的場(chǎng)合. 它被用于頭文件中(#include
既可用于頭文件中, 又可用于 .c
文件中)來(lái)包含其他的頭文件. 而且包含頭文件的路徑比較特殊: 從當(dāng)前頭文件所在目錄之后的目錄來(lái)搜索頭文件。
比如:
若頭文件的搜索路徑依次為 =A,B,C,D,E=, 而 =#include_next= 所在的當(dāng)前頭文件位于 =B= 目錄,
那么 =#include_next= 使得預(yù)處理器從 =C,D,E= 目錄來(lái)搜索 =#include_next= 所指定的頭文件。
可參考預(yù)處理命令 cpp
的手冊(cè)進(jìn)一步了解 #include_next
。
4、預(yù)定義宏
標(biāo)準(zhǔn)C中定義了一些對(duì)象宏, 這些宏的名稱以 __
開(kāi)頭和結(jié)尾, 并且都是大寫(xiě)字符. 這些預(yù)定義宏可以被 #undef
, 也可以被重定義。
下面列出一些標(biāo)準(zhǔn)C中常見(jiàn)的預(yù)定義對(duì)象宏(其中也包含gcc自己定義的一些預(yù)定義宏:
__LINE__ 當(dāng)前語(yǔ)句所在的行號(hào), 以10進(jìn)制整數(shù)標(biāo)注.
__FILE__ 當(dāng)前源文件的文件名, 以字符串常量標(biāo)注.
__DATE__ 程序被編譯的日期, 以"Mmm dd yyyy"格式的字符串標(biāo)注.
__TIME__ 程序被編譯的時(shí)間, 以"hh:mm:ss"格式的字符串標(biāo)注, 該時(shí)間由asctime返回.
__STDC__ 如果當(dāng)前編譯器符合ISO標(biāo)準(zhǔn), 那么該宏的值為1
__STDC_VERSION__ 如果當(dāng)前編譯器符合C89, 那么它被定義為199409L, 如果符合C99, 那么被定義為199901L.
我用gcc, 如果不指定-std=c99, 其他情況都給出__STDC_VERSION__未定義的錯(cuò)誤信息, 咋回事呢?
__STDC_HOSTED__ 如果當(dāng)前系統(tǒng)是"本地系統(tǒng)(hosted)", 那么它被定義為1. 本地系統(tǒng)表示當(dāng)前系統(tǒng)擁有完整的標(biāo)準(zhǔn)C庫(kù).
另外, gcc
定義的預(yù)定義宏:
__OPTMIZE__ 如果編譯過(guò)程中使用了優(yōu)化, 那么該宏被定義為1.
__OPTMIZE_SIZE__ 同上, 但僅在優(yōu)化是針對(duì)代碼大小而非速度時(shí)才被定義為1.
__VERSION__ 顯示所用gcc的版本號(hào).
具體可參考 GCC the complete reference
。
要想看到 gcc
所定義的所有預(yù)定義宏, 可以運(yùn)行:
$cpp -dM /dev/null
5、擴(kuò)展控制
#line
#line
指令用來(lái)修改 __LINE__
和 __FILE__
的內(nèi)容。前面介紹了, __LINE__和__FILE__
都是編譯程序中預(yù)定義的標(biāo)識(shí)符。標(biāo)識(shí)符 __LINE__
的內(nèi)容是當(dāng)前被編譯代碼行的行號(hào),=__FILE__= 的內(nèi)容是當(dāng)前被編譯源文件的文件名。 #line
的一般形式是:
#line number “filename”
其中, number
是正整數(shù)并變成 __LINE__
的新值;可選的 filename
是合法文件標(biāo)識(shí)符并變成 __FILE__
的新值。 #line
主要用于調(diào)試和特殊應(yīng)用。
常見(jiàn)的使用:
printf("line: %d, file: %s\n", __LINE__, __FILE__);
#line 100 "haha"
printf("line: %d, file: %s\n", __LINE__, __FILE__);
printf("line: %d, file: %s\n", __LINE__, __FILE__);
以上代碼,輸出顯示:
line: 34, file: 1.c
line: 100, file: haha
line: 101, file: haha
一個(gè)完整的例子, main.cpp
文件內(nèi)容如下:
1 #include<iostream>
2 using std::cout;
3 using std::cin;
4 using std::endl;
5 #line 2 "myfile.h"
6 int main(int argc, char *argv[])
7 {
8 cout<<__LINE__<<":"<<__FILE__<<endl;
9 return 0;
10 }
上述代碼編譯運(yùn)行輸出如下:
[quietheart@lv-k pre_test]$ ls
main.cpp
[quietheart@lv-k pre_test]$ make main
g++ main.cpp -o main
[quietheart@lv-k pre_test]$ ls
main main.cpp
[quietheart@lv-k pre_test]$ ./main
4:myfile.h
由上面輸出可以看出,
-
__LINE__
的內(nèi)容變成了相對(duì)#line
宏的內(nèi)容,即#line
下面的第一行就是#line
指定的2; - 同時(shí)
__FILE__
變成了#line
指定的myfile.h
。
#pragma, _Pragma
#pragma
編譯器用來(lái)添加新的預(yù)處理功能或者顯示一些編譯信息. 是編譯程序?qū)崿F(xiàn)時(shí)定義的指令,它允許由此向編譯程序傳入各種指令。
例如:
一個(gè)編譯程序可能具有支持跟蹤程序執(zhí)行的選項(xiàng),此時(shí)可以用 =#pragma= 語(yǔ)句選擇該功能。
編譯程序忽略其不支持的 #pragma
選項(xiàng), #pragma
提高C源程序?qū)幾g程序的可移植性。
#pragma
的格式是各編譯器特定的, gcc
的如下:
#pragma GCC name token(s)
#pragma
之后有兩個(gè)部分: GCC
和特定的 pragma name
。
下面分別介紹 gcc
中常用的。
(1) #pragma GCC dependency
dependency
測(cè)試當(dāng)前文件(既該語(yǔ)句所在的程序代碼)與指定文件(既 #pragma
語(yǔ)句最后列出的文件)的時(shí)間戳。 如果指定文件比當(dāng)前文件新, 則給出警告信息。
e.g. 在 demo.c
中給出這樣一句:
#pragma GCC dependency "temp-file"
然后在 demo.c
所在的目錄新建一個(gè)更新的文件:
$touch temp-file
編譯:
$gcc demo.c
會(huì)給出這樣的警告信息: warning: current file is older than temp-file
如果當(dāng)前文件比指定的文件新, 則不給出任何警告信息。
還可以在在 #pragma
中給添加自定義的警告信息。
e.g.
#pragma GCC dependency "temp-file" "demo.c needs to be updated!"
可能會(huì)有如下警告
1.c:27:38: warning: extra tokens at end of #pragma directive
1.c:27:38: warning: current file is older than temp-file
注意: 后面新增的警告信息要用""引用起來(lái), 否則 gcc
將給出警告信息。
(2) #pragma GCC poison token(s)
若源代碼中出現(xiàn)了 #pragma
中給出的 token(s)
, 則編譯時(shí)顯示警告信息. 它一般用于在調(diào)用你不想使用的函數(shù)時(shí)候給出出錯(cuò)信息。
e.g.
#pragma GCC poison scanf
scanf("%d", &a);
會(huì)輸出如下:
warning: extra tokens at end of #pragma directive
error: attempt to use poisoned "scanf"
注意, 如果調(diào)用了 poison
中給出的標(biāo)記, 那么編譯器會(huì)給出的是出錯(cuò)信息. 關(guān)于第一條警告, 我還不知道怎么避免, 用""將 token(s)
引用起來(lái)也不行.
(3) #pragma GCC system_header
從 #pragma GCC system_header
直到文件結(jié)束之間的代碼會(huì)被編譯器視為系統(tǒng)頭文件之中的代碼. 系統(tǒng)頭文件中的代碼往往不能完全遵循C標(biāo)準(zhǔn), 所以頭文件之中的警告信息往往不顯示. (除非用 #warning
顯式指明)。
(這條 #pragma
語(yǔ)句還沒(méi)發(fā)現(xiàn)用什么大的用處)
_Pragma
實(shí)現(xiàn)將 #pragma
用于宏擴(kuò)展
由于 #pragma
不能用于宏擴(kuò)展, 所以gcc還提供了 _Pragma
:
e.g.
#define PRAGMA_DEP #pragma GCC dependency "temp-file"
由于預(yù)處理之時(shí)進(jìn)行一次宏擴(kuò)展, 采用上面的方法會(huì)在編譯時(shí)引發(fā)錯(cuò)誤, 要將 #pragma
語(yǔ)句定義成一個(gè)宏擴(kuò)展, 應(yīng)該使用下面的 _Pragma
語(yǔ)句:
#define PRAGMA_DEP _Pragma("GCC dependency \"temp-file\"")
注意, ()
中包含的""引用之前引該加上\轉(zhuǎn)義字符.
#warning, #error
#warning, #error
分別用于在編譯時(shí)顯示警告和錯(cuò)誤信息, 格式如下:
#warning tokens
#error tokens
注意, #error
和 #warning
后的 token
要用""引用起來(lái)!
在 gcc
中,
- 如果給出了
warning
, 編譯繼續(xù)進(jìn)行, - 但若給出了
error
, 則編譯停止, - 若在命令行中指定了
-Werror
(gcc
手冊(cè)上意思是將所有warning
變?yōu)?error
), 即使只有警告信息, 也不編譯.
#warning
指令
e.g.
#warning "some warning"
error
指令
#error
指令強(qiáng)制編譯程序停止編譯,它主要用于程序調(diào)試。 #error
指令的一般形式是:
#error error-message
注意,宏串 error-message
不用雙引號(hào)包圍。遇到 #error
指令時(shí),錯(cuò)誤信息被顯示,可能同時(shí)還顯示編譯程序作者預(yù)先定義的其他內(nèi)容。
例如 main.cpp
文件內(nèi)容如下:
1 #include<iostream>
2 using std::cout;
3 using std::cin;
4 using std::endl;
5 #ifdef MMM
6 #error myerror //如果執(zhí)行了這個(gè)就會(huì)阻止編譯通過(guò)
7 #endif
8 int main(int argc, char *argv[])
9 {
10 cout<<"hello world!"<<endl;
11 return 0;
12 }
編譯過(guò)程如下:
[quietheart@lv-k pre_test]$ ls
main.cpp
[quietheart@lv-k pre_test]$ make CXXFLAGS+=-DMMM pre_error
g++ -DMMM pre_error.cpp -o pre_error
pre_error.cpp:6:2: error: #error myerror
#error myerror //如果執(zhí)行了這個(gè)就會(huì)阻止編譯通過(guò)
^~~~~
<內(nèi)置>: recipe for target 'pre_error' failed
make: *** [pre_error] Error 1
[quietheart@lv-k pre_test]$ ls
main.cpp
[quietheart@lv-k pre_test]$ make main
g++ main.cpp -o main
[quietheart@lv-k pre_test]$ ls
main main.cpp
[quietheart@lv-k pre_test]$ ./main
hello world!
C語(yǔ)言常用宏定義
這里給出C語(yǔ)言中一些常用的宏定義。
01: 防止一個(gè)頭文件被重復(fù)包含
#ifndef COMDEF_H
#define COMDEF_H
//頭文件內(nèi)容
#endif
02: 重新定義一些類型,防止由于各種平臺(tái)和編譯器的不同,而產(chǎn)生的類型字節(jié)數(shù)差異,方便移植。
typedef unsigned char boolean; /* Boolean value type. */
typedef unsigned long int uint32; /* Unsigned 32 bit value */
typedef unsigned short uint16; /* Unsigned 16 bit value */
typedef unsigned char uint8; /* Unsigned 8 bit value */
typedef signed long int int32; /* Signed 32 bit value */
typedef signed short int16; /* Signed 16 bit value */
typedef signed char int8; /* Signed 8 bit value */
//下面的不建議使用
typedef unsigned char byte; /* Unsigned 8 bit value type. */
typedef unsigned short word; /* Unsinged 16 bit value type. */
typedef unsigned long dword; /* Unsigned 32 bit value type. */
typedef unsigned char uint1; /* Unsigned 8 bit value type. */
typedef unsigned short uint2; /* Unsigned 16 bit value type. */
typedef unsigned long uint4; /* Unsigned 32 bit value type. */
typedef signed char int1; /* Signed 8 bit value type. */
typedef signed short int2; /* Signed 16 bit value type. */
typedef long int int4; /* Signed 32 bit value type. */
typedef signed long sint31; /* Signed 32 bit value */
typedef signed short sint15; /* Signed 16 bit value */
typedef signed char sint7; /* Signed 8 bit value */
03: 得到指定地址上的一個(gè)字節(jié)或字
#define MEM_B(x) (*((byte *)(x)))
#define MEM_W(x) (*((word *)(x)))
04: 求最大值和最小值
#define MAX(x,y) (((x)>(y)) ? (x) : (y))
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
05: 得到一個(gè)field在結(jié)構(gòu)體(struct)中的偏移量
#define FPOS(type,field) ((dword)&((type *)0)->field)
06: 得到一個(gè)結(jié)構(gòu)體中field所占用的字節(jié)數(shù)
#define FSIZ(type,field) sizeof(((type *)0)->field)
07: 按照LSB格式把兩個(gè)字節(jié)轉(zhuǎn)化為一個(gè)Word
#define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])
08: 按照LSB格式把一個(gè)Word轉(zhuǎn)化為兩個(gè)字節(jié)
#define FLOPW(ray,val) (ray)[0] = ((val)/256); (ray)[1] = ((val) & 0xFF)
09: 得到一個(gè)變量的地址(word寬度)
#define B_PTR(var) ((byte *) (void *) &(var))
#define W_PTR(var) ((word *) (void *) &(var))
10: 得到一個(gè)字的高位和低位字節(jié)
#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
11: 返回一個(gè)比X大的最接近的8的倍數(shù)
#define RND8(x) ((((x) + 7)/8) * 8)
12: 將一個(gè)字母轉(zhuǎn)換為大寫(xiě)
#define UPCASE(c) (((c)>='a' && (c) <= 'z') ? ((c) - 0x20) : (c))
13: 判斷字符是不是10進(jìn)值的數(shù)字
#define DECCHK(c) ((c)>='0' && (c)<='9')
14: 判斷字符是不是16進(jìn)值的數(shù)字
#define HEXCHK(c) (((c) >= '0' && (c)<='9') ((c)>='A' && (c)<= 'F') \
((c)>='a' && (c)<='f'))
15: 防止溢出的一個(gè)方法
#define INC_SAT(val) (val=((val)+1>(val)) ? (val)+1 : (val))
16: 返回?cái)?shù)組元素的個(gè)數(shù)
#define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))
17: 返回一個(gè)無(wú)符號(hào)數(shù)n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
#define MOD_BY_POWER_OF_TWO( val, mod_by ) ((dword)(val) & (dword)((mod_by)-1))
18: 對(duì)于IO空間映射在存儲(chǔ)空間的結(jié)構(gòu),輸入輸出處理
#define inp(port) (*((volatile byte *)(port)))
#define inpw(port) (*((volatile word *)(port)))
#define inpdw(port) (*((volatile dword *)(port)))
#define outp(port,val) (*((volatile byte *)(port))=((byte)(val)))
#define outpw(port, val) (*((volatile word *)(port))=((word)(val)))
#define outpdw(port, val) (*((volatile dword *)(port))=((dword)(val)))
19: 使用一些宏跟蹤調(diào)試
ANSI標(biāo)準(zhǔn)說(shuō)明了五個(gè)預(yù)定義的宏名。它們是:
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__
C++中還定義了 __cplusplus
如果編譯器不是標(biāo)準(zhǔn)的,則可能僅支持以上宏名中的幾個(gè),或根本不支持。記住編譯程序也許還提供其它預(yù)定義的宏名。
__LINE__ 及 __FILE__ 宏指示,#line指令可以改變它的值,簡(jiǎn)單的講,編譯時(shí),它們包含程序的當(dāng)前行數(shù)和文件名。
__DATE__ 宏指令含有形式為月/日/年的串,表示源文件被翻譯到代碼時(shí)的日期。
__TIME__ 宏指令包含程序編譯的時(shí)間。時(shí)間用字符串表示,其形式為: 分:秒
__STDC__ 宏指令的意義是編譯時(shí)定義的。一般來(lái)講,如果__STDC__已經(jīng)定義,編譯器將僅接受不包含任何非標(biāo)準(zhǔn)擴(kuò)展的標(biāo)準(zhǔn)C/C++代碼。如果實(shí)現(xiàn)是標(biāo)準(zhǔn)的,則宏__STDC__含有十進(jìn)制常量1。如果它含有任何其它數(shù),則實(shí)現(xiàn)是非標(biāo)準(zhǔn)的。
__cplusplus 與標(biāo)準(zhǔn)c++一致的編譯器把它定義為一個(gè)包含至少6為的數(shù)值。與標(biāo)準(zhǔn)c++不一致的編譯器將使用具有5位或更少的數(shù)值。
可以定義宏,例如:當(dāng)定義了 _DEBUG
,輸出數(shù)據(jù)信息和所在文件所在行
#ifdef _DEBUG
#define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
#else
#define DEBUGMSG(msg,date)
#endif
20:宏定義防止錯(cuò)誤使用小括號(hào)包含。
例如:
有問(wèn)題的定義:
#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr); bufp += nr;}
應(yīng)該使用的定義:
#difne DO(a,b) do{a+b;a++;}while(0)
例如:
if(addr)
DUMP_WRITE(addr,nr);
else
do_somethong_else();
宏展開(kāi)以后變成這樣:
if(addr)
{memcpy(bufp,addr,nr); bufp += nr;};
else
do_something_else();
其它
主要參考: