內聯(lián)函數與宏定義

內聯(lián)函數與宏定義

在C中,常用預處理語句#define來代替一個函數定義。例如:
#define MAX(a,b) ((a)>(b)?(a):(b))

該語句使得程序中每個出現MAX(a,b)函數調用的地方都被宏定義中后面的表達式((a)>(b)?(a):(b))所替換。

宏定義語句的書寫格式有過分的講究,MAX與括號之間不能有空格,所有的參數都要放在括號里。盡管如此,它還是有麻煩:

int a=1,b=0;
MAX(a++,b); //a被增值2次
MAX(a++,b+10); //a被增值1次
MAX(a,"Hello");//錯誤地比較int和字符串,沒有參數類型檢查

MAX( )函數的求值會由于兩個參數值的大小不同而產生不同的副作用。MAX(a++,b)的值為2,同時a的值為3;MAX(a++,b+10)的值為10,同時a的值為2。如果是普通函數,則MAX(a,"HellO")會受到函數調用的檢查,但此處不會因為兩個參數類型不同而被編譯拒之門外。幸運的是,通過一個內聯(lián)函數可以得到所有宏的替換效能和所有可預見的狀態(tài)以及常規(guī)函數的類型檢查

inline int MAX(int a,int b)
{
  return a>b?a:b;
}
  1. 內聯(lián)函數與宏的區(qū)別:

    傳統(tǒng)的宏定義函數可能會引起一些麻煩。

    ex:

    #define F(x) x+x
      
    void main(){int i=1;F(i++);}
    

    這里x將被加兩次。

    內聯(lián)函數被編譯器自動的用函數的形勢添加進代碼,而不會出現這種情況。

    內聯(lián)函數的使用提高了效率(省去了很多函數調用匯編代碼如:call和ret等)。

  2. 內聯(lián)函數的使用:

    所有在類的聲明中定義的函數將被自動認為是內聯(lián)函數。

    class A()
    {
        void c();// not a inline function;
    
        void d(){ 
            print("d() is a inline function.");
        }
    }
    

    如果想將一個全局函數定義為內聯(lián)函數可用,inline 關鍵字。

    inline a(){
        print("a() is a inline function.");
    }
    

    注意:

    在內聯(lián)函數中如果有復雜操作將不被內聯(lián)。如:循環(huán)和遞歸調用。

    總結:

    將簡單短小的函數定義為內聯(lián)函數將會提高效率。

用內聯(lián)取代宏代碼

C++語言支持函數內聯(lián),其目的是為了提高函數的執(zhí)行效率(速度)。
在C程序中,可以用宏代碼提高執(zhí)行效率。宏代碼本身不是函數,但使用起來象函數。
預處理器用復制宏代碼的方式代替函數調用,省去了參數壓棧、生成匯編語言的CALL調用、返回參數、執(zhí)行return等過程,從而提高了速度。 使用宏代碼最大的缺點是容易出錯,預處理器在復制宏代碼時常常產生意想不到的邊際效應。例如

#define MAX(a, b)       (a) > (b) ? (a) : (b) 

語句

result = MAX(i, j) + 2 ; 

將被預處理器解釋為

result = (i) > (j) ? (i) : (j) + 2 ; 

由于運算符‘+’比運算符‘:’的優(yōu)先級高,所以上述語句并不等價于期望的

result = ( (i) > (j) ? (i) : (j) ) + 2 ; 

如果把宏代碼改寫為

#define MAX(a, b)       ( (a) > (b) ? (a) : (b) ) 

則可以解決由優(yōu)先級引起的錯誤。但是即使使用修改后的宏代碼也不是萬無一失的,例如語句

result = MAX(i++, j); 

將被預處理器解釋為

result = (i++) > (j) ? (i++) : (j); 

對于C++ 而言,使用宏代碼還有另一種缺點:無法操作類的私有數據成員。

讓我們看看C++的“函數內聯(lián)”是如何工作的。

對于任何內聯(lián)函數,編譯器在符號表里放入函數的聲明(包括名字、參數類型、返回值類型)。如果編譯器沒有發(fā)現內聯(lián)函數存在錯誤,那么該函數的代碼也被放入符號表里。在調用一個內聯(lián)函數時,編譯器首先檢查調用是否正確(進行類型安全檢查,或者進行自動類型轉換,當然對所有的函數都一樣)。如果正確,內聯(lián)函數的代碼就會直接替換函數調用,于是省去了函數調用的開銷。

這個過程與預處理有顯著的不同,因為預處理器不能進行類型安全檢查,或者進行自動類型轉換。假如內聯(lián)函數是成員函數,對象的地址(this)會被放在合適的地方,這也是預處理器辦不到的。
C++語言的函數內聯(lián)機制既具備宏代碼的效率,又增加了安全性,而且可以自由操作類的數據成員。

所以在C++程序中,應該用內聯(lián)函數取代所有宏代碼,“斷言assert”恐怕是唯一的例外。assert是僅在Debug版本起作用的宏,它用于檢查“不應該”發(fā)生的情況。為了不在程序的Debug版本和Release版本引起差別,assert不應該產生任何副作用。如果assert是函數,由于函數調用會引起內存、代碼的變動,那么將導致Debug版本與Release版本存在差異。所以assert不是函數,而是宏。

內聯(lián)函數的編程風格

關鍵字inline必須與函數定義體放在一起才能使函數成為內聯(lián),僅將inline放在函數聲明前面不起任何作用。如下風格的函數Foo不能成為內聯(lián)函數:

inline void Foo(int x, int y);  // inline僅與函數聲明放在一起 
void Foo(int x, int y) 
{ 
… 
} 

而如下風格的函數Foo則成為內聯(lián)函數:

void Foo(int x, int y);  
inline void Foo(int x, int y) // inline與函數定義體放在一起 
{ 
… 
} 

所以說,inline是一種
“用于實現的關鍵字”
,而不是一種
“用于聲明的關鍵字”
。一般地,用戶可以閱讀函數的聲明,但是看不到函數的定義。盡管在大多數教科書中內聯(lián)函數的聲明、定義體前面都加了inline關鍵字,但我認為inline不應該出現在函數的聲明中。這個細節(jié)雖然不會影響函數的功能,但是體現了高質量C++/C程序設計風格的一個基本原則:聲明與定義不可混為一談,用戶沒有必要、也不應該知道函數是否需要內聯(lián)。

定義在類聲明之中的成員函數將自動地成為內聯(lián)函數,例如

class A 
{ 
public: 
void Foo(int x, int y) { … }  // 自動地成為內聯(lián)函數 
} 

將成員函數的定義體放在類聲明之中雖然能帶來書寫上的方便,但不是一種良好的編程風格,上例應該改成:

// 頭文件 
class A 
{ 
public: 
void Foo(int x, int y);  
} 
// 定義文件 
inline void A::Foo(int x, int y) 
{ 
… 
}

慎用內聯(lián)

內聯(lián)能提高函數的執(zhí)行效率,為什么不把所有的函數都定義成內聯(lián)函數?

如果所有的函數都是內聯(lián)函數,還用得著“內聯(lián)”這個關鍵字嗎?

內聯(lián)是以代碼膨脹(復制)為代價,僅僅省去了函數調用的開銷,從而提高函數的執(zhí)行效率。如果執(zhí)行函數體內代碼的時間,相比于函數調用的開銷較大,那么效率的收獲會很少。另一方面,每一處內聯(lián)函數的調用都要復制代碼,將使程序的總代碼量增大,消耗更多的內存空間。

以下情況不宜使用內聯(lián):

  • 如果函數體內的代碼比較長,使用內聯(lián)將導致內存消耗代價較高。
  • 如果函數體內出現循環(huán),那么執(zhí)行函數體內代碼的時間要比函數調用的開銷大。

類的構造函數和析構函數容易讓人誤解成使用內聯(lián)更有效。要當心構造函數和析構函數可能會隱藏一些行為,如“偷偷地”執(zhí)行了基類或成員對象的構造函數和析構函數。

所以不要隨便地將構造函數和析構函數的定義體放在類聲明中。
一個好的編譯器將會根據函數的定義體,自動地取消不值得的內聯(lián)(這進一步說明了inline不應該出現在函數的聲明中)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容