c++中內聯函數那點事

1 內聯(inline)函數

    c++在編譯時可以講調用的函數代碼嵌入到主調函數中,這種嵌入到主調函數中的函數稱爲內聯函數,又稱爲內嵌函數或內置函數。

  • 定義內聯函數時,在函數定義和函數原型聲明時都使用inline,也可以只在其中一處使用,其效果一樣。
  • 內聯函數在編譯時用內聯函數函數的函數體替換,所以不發生函數調用,不需要保護現場,恢復現場,節省了開銷。
  • 內聯函數增加了目標程序的代碼量。因此,一般只將函數規模很小且使用頻繁的函數聲明爲內聯函數。
  • 當內聯函數中實現過於複雜時,編譯器會將它作爲一個普通函數處理,所以內聯函數內不能包含循環語句和switch語句。

內聯函數格式如下:

inline 函數類型 函數名(形參列表)
{
	函數體;
}

inline int add(int a, int b)
{
	return a + b;
}

2 洞悉內聯函數底層原理

1.使用Visual Studio 2015創建一個C++Win32控制檯程序,點擊項目->項目屬性設置內聯函數優化
在這裏插入圖片描述
2.編寫內聯函數代碼,設置斷點,debug啓動

#include <iostream>
#include <string>
using namespace std;
inline int add(int a, int b)
{
	return a + b;//斷點1
}
int main()
{

	int result = add(12, 34);
	cout << result << endl;//斷點2
	return 0;
}

3.調試->窗口->反彙編,然後就能看到編譯後的彙編程序

...

		int result = add(12, 34);
00B620DE  mov         eax,0Ch  
00B620E3  add         eax,22h  //對eax中和22h中值進行相加,賦值給eax
00B620E6  mov         dword ptr [result],eax  
		cout << result << endl;
00B620E9  mov         esi,esp  
00B620EB  push        offset std::endl<char,std::char_traits<char> > (0B610A5h)  
00B620F0  mov         edi,esp  
00B620F2  mov         eax,dword ptr [result]  
00B620F5  push        eax  
00B620F6  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B6D098h)]  
00B620FC  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0A8h)]  
00B62102  cmp         edi,esp  
00B62104  call        __RTC_CheckEsp (0B611C7h)  
00B62109  mov         ecx,eax  
00B6210B  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0ACh)]  
00B62111  cmp         esi,esp  
00B62113  call        __RTC_CheckEsp (0B611C7h)   
		return 0;

4.從彙編代碼中可以代碼編譯後內聯函數直接嵌入到主函數中,並且斷點1不會執行到,下面是沒使用內聯函數(去掉inline關鍵字)的彙編代碼:

int result = add(12, 34);
00291A4E  push        22h  
00291A50  push        0Ch  
00291A52  call        add (02914D8h)  //調用add函數
00291A57  add         esp,8//移動堆棧指針esp,繼續執行主函數
00291A5A  mov         dword ptr [result],eax  
		cout << result << endl;
00291A5D  mov         esi,esp  
00291A5F  push        offset std::endl<char,std::char_traits<char> > (02910A5h)  
00291A64  mov         edi,esp  
00291A66  mov         eax,dword ptr [result]  
00291A69  push        eax  
00291A6A  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (029D098h)]  
		cout << result << endl;
00291A70  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0A8h)]  
00291A76  cmp         edi,esp  
00291A78  call        __RTC_CheckEsp (02911C7h)  
00291A7D  mov         ecx,eax  
00291A7F  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0ACh)]  
00291A85  cmp         esi,esp  
00291A87  call        __RTC_CheckEsp (02911C7h)  
		system("pause");
00291A8C  mov         esi,esp  
00291A8E  push        offset string "pause" (0299B30h)  
00291A93  call        dword ptr [__imp__system (029D1DCh)]  
00291A99  add         esp,4  
00291A9C  cmp         esi,esp  
00291A9E  call        __RTC_CheckEsp (02911C7h)  
		return 0;

從以上代碼代碼可以看出,在主函數中調用(call)了add函數。
4.在內聯函數中添加幾個循環後,編譯器就把內聯函數當做普通函數看待了,代碼如下:

inline int add(int a, int b)
{
	
	int sum = 0;
	for (int i = 0; i < 100; i++)
		a++;
	for (int i = 0; i < 100; i++)
	{
		
		for (int i = 0; i < 100; i++)
		{
			sum++;
		}
	}
	
	return a + b;
}
int main()
{
	int result = add(12, 34);
	cout << result << endl;
	return 0;
}
	int result = add(12, 34);
00181A4E  push        22h  
00181A50  push        0Ch  
00181A52  call        add (01814ECh)  ///
00181A57  add         esp,8  
00181A5A  mov         dword ptr [result],eax  
	cout << result << endl;
00181A5D  mov         esi,esp  
00181A5F  push        offset std::endl<char,std::char_traits<char> > (01810A5h)  
00181A64  mov         edi,esp  
00181A66  mov         eax,dword ptr [result]  
00181A69  push        eax  
00181A6A  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (018D098h)]  
	cout << result << endl;
00181A70  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (018D0A8h)]  
00181A76  cmp         edi,esp  
00181A78  call        __RTC_CheckEsp (01811C7h)  
00181A7D  mov         ecx,eax  
00181A7F  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (018D0ACh)]  
00181A85  cmp         esi,esp  
00181A87  call        __RTC_CheckEsp (01811C7h)  
	return 0;
00181AA3  xor         eax,eax  

當內聯函數中實現過於複雜時,編譯器會將它作爲一個普通函數處理。

3 函數調用爲什麼會增加程序運行開銷?

    在程序中進行函數調用時,底層到底發生了哪些事情?程序被編譯成目標程序,目標程序中指令按照順序存放在計算機存儲器中,程序運行時,有這麼個指針,始終指向程序當前運行的執行語句,當調用程序時,它會指向被調函數指令的位置,調用完畢後再指回來,繼續主函數執行。
    在這個兩次改變指向的過程中,會有這麼個問題,我指向被調函數之行爲,再怎麼指回來,是不是需要把主函數的地址記錄下來,方便指針”回家不迷路“;這個過程有一個專業的名稱“保護現場”,保護現場保護的不止這一個指針變量,還有操作數、堆棧地址等,當被調函數執行完畢後,重新把這些變量放到它原來的位置,繼續執行,這個過程稱爲“恢復現場”,保護現場和恢復現場會佔用空間和時間,因而會增加程序運行開銷。不過當被調函數複雜(運行時間足夠長),保護現場和恢復現場的時間微不足道,編輯器就會把內聯函數看成普通函數調用,縮短了主函數指令量,主函數指令量太多,又會涉及到頁面置換的問題,如果有想比較更深層次第理解,可以參考計算機組成原理和操作系統的相關知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章