一個宏定義看看你的 C語言級別。
今天我們來看一下利用宏定義編寫類似函數(shù)調(diào)用的方法和真實的函數(shù)有什么區(qū)別,來進一步理解宏定義的應(yīng)用。
一、宏和函數(shù)怎么選?
首先來看一個例子:
#define N 2+2
void main()
{
int a = N * N;
printf( "a = %d", a);
}
這里,我們得理解宏的工作方式,它是在編譯器編譯代碼之前做的一個純文本的替換工作,因此,有時候簡單宏替換的結(jié)果就會和我們預(yù)想的結(jié)果出現(xiàn)偏差。
比如上面的例子,我們預(yù)期 N 為 4,a=16,但實際結(jié)果卻為 a=8;原因在于宏的作用方式是傻瓜式的文本替換。
在編譯之前,編譯器首先將宏定義的文本替換到程序體中,這個替換是完全無腦的一個操作,看一下替換結(jié)果就知道了。
#define N 2+2
void main()
{
int a = 2 + 2 * 2 + 2;
printf( "a = %d", a);
}
這也就是我們上一篇文章中講到的,寫宏函數(shù)的時候一定要注意括號的應(yīng)用,多加括號一定是利大于弊的。
二、預(yù)處理宏的優(yōu)缺點
在軟件開發(fā)過程中,經(jīng)常有一些常用或者通用的功能或者代碼段,這些功能既可以寫成函數(shù),也可以封裝成為宏定義。那么究竟是用函數(shù)好,還是宏定義好?
我們還是看上一篇文章中引用的比較大小的例子:
#define MAX( a, b) ( (a) > (b) ? (a) : (b) )
//把它用函數(shù)來實現(xiàn):
int max( int a, int b)
{
return (a > b ? a : b);
}
如果我們在程序中將要使用比較大小的函數(shù),我們顯然會選用上面的宏定義,理由如下:
1 首先,函數(shù)調(diào)用會帶來額外的開銷,他需要開辟新的??臻g,記錄返回值,還需要將形參壓入棧中,函數(shù)返回時還需要釋放堆??臻g。
這樣的開銷不僅會讓程序執(zhí)行效率變低,代碼量也會大大增加,因此使用上面的宏函數(shù)做文本替代就顯得更明智。
2 其次,函數(shù)的形參被聲明成了一個特定的類型,如例子中是 int,這樣如果我們軟件中需要使用浮點型的比較大小,我們就不得不重寫一個函數(shù),從這一點也可以看到宏函數(shù)的優(yōu)勢。
因為是文本的替換,因此他與類型也沒有關(guān)系,不過類型不對應(yīng),會在編譯階段的時候報錯,這點還是具備利用價值的。
3 另外,還有一些任務(wù)根本無法用函數(shù)實現(xiàn),但是用宏定義卻很好實現(xiàn)。
比如參數(shù)類型沒法作為參數(shù)傳遞給函數(shù),但是可以把參數(shù)類型傳遞給帶參的宏。
看下面的例子:
#define MALLOC(n, type)((type ) malloc((n)sizeof(type)))
利用這個宏,我們就可以為任何類型分配一段我們指定的空間大小,并返回指向這段空間的指針。我們可以觀察一下這個宏確切的工作過程:
int *ptr;
ptr = MALLOC ( 5, int );
//將這宏展開以后的結(jié)果:
ptr = (int *) malloc ((5) * sizeof(int));
這個例子是宏定義的經(jīng)典應(yīng)用之一,完成了函數(shù)不能完成的功能,但是宏定義也不能濫用,通常,如果相同的代碼需要出現(xiàn)在程序的幾個地方,更好的方法是把它實現(xiàn)為一個函數(shù)。
三、宏的缺陷,內(nèi)聯(lián)函數(shù)的引入
宏雖然有著一定的優(yōu)勢,但是它的缺點也不可忽視。
在編譯階段,我們很難發(fā)現(xiàn)代碼哪里出問題了,因為宏替換是發(fā)生在預(yù)處理階段,所以有時候在宏函數(shù)傳參的時候發(fā)生一些錯誤,編譯器不會發(fā)現(xiàn),那它調(diào)試起來就很麻煩。
所以為了解決這種不利于調(diào)試的問題,就有了內(nèi)聯(lián)函數(shù)。
那么什么是內(nèi)聯(lián)函數(shù)呢?
我們以inline修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯階段,C編譯器會在調(diào)用函數(shù)的地方直接把函數(shù)展開,沒有壓棧開銷,內(nèi)聯(lián)函數(shù)提升程序運行效率,但是會相應(yīng)的增加代碼的長度。所以這里叫做空間換時間。
道之初,帶來了空間和時間,所以,空間和時間就是編程的陰陽兩級。
不懂編程之道的程序員常常把空間和時間消耗殆盡,得道的程序員則總是有足夠的空間和時間完成編程任務(wù)。
舉個例子
inline int Add(int a,int b)
{
return a+b;
}
編譯期間,編譯器會將內(nèi)聯(lián)函數(shù)替換相應(yīng)的函數(shù)體;
這里要注意一點,在函數(shù)前加 inline 只是建議編譯器當作內(nèi)容函數(shù)處理 ,但編譯器有自己的主張(遞歸 ,復(fù)雜函數(shù)等)
內(nèi)聯(lián)函數(shù)的特性:
inline是一種以空間換時間的做法,省去調(diào)用函數(shù)中參數(shù)壓棧,減少了調(diào)用的開銷。同時,使用內(nèi)聯(lián)函數(shù)也比宏函數(shù)更省心,不必擔心宏參數(shù)傳遞過程中出現(xiàn)的意外情況。
inline對于編譯器而言只是一個建議,編譯器會自動優(yōu)化,如果定義為inline的函數(shù)體內(nèi)有循環(huán)/遞歸等等,編譯器優(yōu)化時會忽略掉內(nèi)聯(lián),另外,如果內(nèi)聯(lián)函數(shù)的函數(shù)體過大,一般的編譯器也會放棄內(nèi)聯(lián)方式,采用普通調(diào)用的方式進行函數(shù)調(diào)用。
inline不建議聲明和定義分離,分離會導(dǎo)致鏈接錯誤。因為inline被展開,就沒有函數(shù)地址了,鏈接就會找不到。