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