- 相關(guān)推薦
C語(yǔ)言中的預(yù)編譯宏定義
導(dǎo)語(yǔ):C初學(xué)者可能對(duì)預(yù)處理器沒(méi)什么概念, 這是情有可原,下面是C中的預(yù)編譯宏定義,一起來(lái)學(xué)習(xí)下吧:
(一) 預(yù)處理命令簡(jiǎn)介
預(yù)處理命令由#(hash字符)開(kāi)頭, 它獨(dú)占一行, #之前只能是空白符. 以#開(kāi)頭的語(yǔ)句就是預(yù)處理命令, 不以#開(kāi)頭的語(yǔ)句為C中的代碼行. 常用的預(yù)處理命令如下:
#define 定義一個(gè)預(yù)處理宏
#undef 取消宏的定義
#include 包含文件命令
#include_next 與#include相似, 但它有著特殊的用途
#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è)宏是否被定義
#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)
1, command預(yù)處理命令的名稱, 它之前以#開(kāi)頭, #之后緊隨預(yù)處理命令, 標(biāo)準(zhǔn)C允許#兩邊可以有空白符, 但比較老的編譯器可能不允許這樣. 若某行中只包含#(以及空白符), 那么在標(biāo)準(zhǔn)C中該行被理解為空白. 整個(gè)預(yù)處理語(yǔ)句之后只能有空白符或者注釋, 不能有其它內(nèi)容.
2, name代表宏名稱, 它可帶參數(shù). 參數(shù)可以是可變參數(shù)列表(C99).
3, 語(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è)宏:
#define MACRO_NAME(args) tokens(opt)
之后出現(xiàn)的MACRO_NAME將被替代為所定義的標(biāo)記(tokens). 宏可帶參數(shù), 而后面的標(biāo)記也是可選的.
對(duì)象宏
不帶參數(shù)的宏被稱為"對(duì)象宏(objectlike macro)"
#define經(jīng)常用來(lái)定義常量, 此時(shí)的宏名稱一般為大寫的字符串. 這樣利于修改這些常量.
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)用該宏, 只需在代碼中指定宏名稱, 該宏將被替代為它被定義的內(nèi)容.
函數(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ū)別在于宏名稱之后不帶括號(hào).
注意, name之后的左括號(hào)(必須緊跟name, 之間不能有空格, 否則這就定義了一個(gè)對(duì)象宏, 它將被替換為 以(開(kāi)始的字符串. 但在調(diào)用函數(shù)宏時(shí), name與(之間可以有空格.
e.g.
#define mul(x,y) ((x)*(y))
注意, 函數(shù)宏之后的參數(shù)要用括號(hào)括起來(lái), 看看這個(gè)例子:
e.g.
#define mul(x,y) x*y
"mul(1, 2+2);" 將被擴(kuò)展為: 1*2 + 2
同樣, 整個(gè)標(biāo)記串也應(yīng)該用括號(hào)引用起來(lái):
e.g.
#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 (stmt) stmt
( a=1; b=2;) 相當(dāng)于在代碼中加入 a=1; b=2 .
( a=1, b=2;) 就有問(wèn)題了: 預(yù)處理器會(huì)提示出錯(cuò): 函數(shù)宏的參數(shù)個(gè)數(shù)不匹配. 預(yù)處理器把","視為參數(shù)間的分隔符.
((a=1, b=2;)) 可解決上述問(wèn)題.
在定義和調(diào)用函數(shù)宏時(shí)候, 要注意一些問(wèn)題:
1, 我們經(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)用.
2, 有的函數(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) 宏可以被多次定義, 前提是這些定義必須是相同的. 這里的"相同"要求先后定義中空白符出現(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) 在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) 宏的替換可以是遞歸的, 所以可以嵌套定義宏.
e.g.
# define ONE NUMBER_1
# define NUMBER_1 1
int a = ONE /* a = 1 */
2, #undef
#undef用來(lái)取消宏定義, 它與#define對(duì)立:
#undef name
如夠被取消的宏實(shí)際上沒(méi)有被#define所定義, 針對(duì)它的#undef并不會(huì)產(chǎn)生錯(cuò)誤.
當(dāng)一個(gè)宏定義被取消后, 可以再度定義它.
3, #if, #elif, #else, #endif
#if, #elif, #else, #endif用于條件編譯:
#if 常量表達(dá)式1
語(yǔ)句...
#elif 常量表達(dá)式2
語(yǔ)句...
#elif 常量表達(dá)式3
語(yǔ)句...
...
#else
語(yǔ)句...
#endif
#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.
注意: #if, #elif, #else之后的宏只能是對(duì)象宏. 如果name為名的宏未定義, 或者該宏是函數(shù)宏. 那么在gcc中使用"-Wundef"選項(xiàng)會(huì)顯示宏未定義的警告信息.
4, #ifdef, #ifndef, defined.
#ifdef, #ifndef, defined用來(lái)測(cè)試某個(gè)宏是否被定義
#ifdef name 或 #ifndef name
它們經(jīng)常用于避免頭文件的重復(fù)引用:
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
#endif
defined(name): 若宏被定義,則返回1, 否則返回0.
它與#if, #elif, #else結(jié)合使用來(lái)判斷宏是否被定義, 乍一看好像它顯得多余, 因?yàn)橐呀?jīng)有了#ifdef和#ifndef. defined用于在一條判斷語(yǔ)句中聲明多個(gè)判別條件:
#if defined(VAX) && defined(UNIX) && !defined(DEBUG)
和#if, #elif, #else不同, #indef, #ifndef, defined測(cè)試的宏可以是對(duì)象宏, 也可以是函數(shù)宏. 在gcc中使用"-Wundef"選項(xiàng)不會(huì)顯示宏未定義的警告信息.
5, #include , #include_next
#include用于文件包含. 在#include 命令所在的行不能含有除注釋和空白符之外的其他任何內(nèi)容.
#include "headfile"
#include
#include 預(yù)處理標(biāo)記
前面兩種形式大家都很熟悉, "#include 預(yù)處理標(biāo)記"中, 預(yù)處理標(biāo)記會(huì)被預(yù)處理器進(jìn)行替換, 替換的結(jié)果必須符合前兩種形式中的某一種.
實(shí)際上, 真正被添加的頭文件并不一定就是#include中所指定的文件. #include"headfile"包含的頭文件當(dāng)然是同一個(gè)文件, 但#include 包包含的"系統(tǒng)頭文件"可能是另外的文件. 但這不值得被注意. 感興趣的話可以查看宏擴(kuò)展后到底引入了哪些系統(tǒng)頭文件.
關(guān)于#include "headfile"和#include 的區(qū)別以及如何在gcc中包含頭文件的詳細(xì)信息, 參考本blog的GCC筆記.
相對(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所指定的頭文件.
可參考cpp手冊(cè)進(jìn)一步了解#include_next
6, 預(yù)定義宏
標(biāo)準(zhǔn)C中定義了一些對(duì)象宏, 這些宏的名稱以"__"開(kāi)頭和結(jié)尾, 并且都是大寫字符. 這些預(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
7, #line
#line用來(lái)修改__LINE__和__FILE__.
e.g.
printf("line: %d, file: %s ", __LINE__, __FILE__);
#line 100 "haha"
printf("line: %d, file: %s ", __LINE__, __FILE__);
printf("line: %d, file: %s ", __LINE__, __FILE__);
顯示:
line: 34, file: 1.c
line: 100, file: haha
line: 101, file: haha
8, #pragma, _Pragma
#pragma用編譯器用來(lái)添加新的預(yù)處理功能或者顯示一些編譯信息. #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!"
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);
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不能用于宏擴(kuò)展, 所以gcc還提供了_Pragma:
e.g.
#define PRAGMA_DEP #pragma GCC dependency "temp-file"
由于預(yù)處理之進(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)義字符.
9, #, ##
#和##用于對(duì)字符串的預(yù)處理操作, 所以他們也經(jīng)常用于printf, puts之類的字符串顯示函數(shù)中.
#用于在宏擴(kuò)展之后將tokens轉(zhuǎn)換為以tokens為內(nèi)容的字符串常量.
e.g.
#define TEST(a,b) printf( #a "<" #b "=%d ", (a)<(b));
注意: #只針對(duì)緊隨其后的token有效!
##用于將它前后的兩個(gè)token組合在一起轉(zhuǎn)換成以這兩個(gè)token為內(nèi)容的字符串常量. 注意##前后必須要有token.
e.g.
#define TYPE(type, n) type n
之后調(diào)用:
TYPE(int, a) = 1;
TYPE(long, b) = 1999;
將被替換為:
int a = 1;
long b = 1999;
(10) #warning, #error
#warning, #error分別用于在編譯時(shí)顯示警告和錯(cuò)誤信息, 格式如下:
#warning tokens
#error tokens
e.g.
#warning "some warning"
注意, #error和#warning后的token要用""引用起來(lái)!
(在gcc中, 如果給出了warning, 編譯繼續(xù)進(jìn)行, 但若給出了error, 則編譯停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不編譯.
【C語(yǔ)言中的預(yù)編譯宏定義】相關(guān)文章:
簡(jiǎn)單講解C語(yǔ)言中宏的定義與使用07-30
C語(yǔ)言宏定義07-01
C 語(yǔ)言中宏的使用08-02
C語(yǔ)言宏定義技巧09-03
C語(yǔ)言的宏定義分析09-10
C語(yǔ)言預(yù)定義宏用法10-06
C語(yǔ)言條件編譯07-22
C語(yǔ)言的編碼編譯08-11