一、函數調用開銷
函數調用會涉及:
參數壓棧(或寄存器傳參)
跳轉到函數體
返回值處理
棧幀銷毀
這個過程對小函數來說可能非常浪費,因此,宏函數和內聯函數的目的就是避免“函數調用的開銷”,通過代碼展開(替換)來實現“零調用成本”。
?二、C語言的宏函數
1、 定義方式
宏函數使用?#define?宏定義語法:
#define SQUARE(x) ((x) * (x))
2、實現原理
預處理器階段完成替換
所有使用?SQUARE(x)?的地方,都會被文本替換為?((x)*(x))(簡單地字符串替換)
不是函數調用,也沒有類型檢查
優點
快!展開是直接替換,沒有函數調用的成本
支持各種類型(無類型限制)
缺點
不安全:可能造成副作用
SQUARE(i++) ?→ ((++i) * (++i)) ?// i 被加了兩次!
#define SQUARE(x) ((x)*(x))int main(){int i = 5;cout << "6*6 = " << SQUARE(++i); }
輸出:
????????6*6 = 42 ??;因為++i加了兩次,所以是6*7;
????????調試困難:宏不是函數,沒法打斷點進去
????????沒有作用域控制:容易污染命名空間
?三、C++ 的內聯函數(inline function)
1、定義方式
inline int square(int x) {return x * x; }
只是在常規的函數定義之前加個 “inline”。
2、實現原理
編譯器在?編譯階段?判斷是否將調用處用函數體替代(不保證一定 inline,只是建議)
真實函數、具有類型檢查、作用域、安全。
利用空間換時間的原理 。
?優點
???????安全、類型檢查完整
可以用調試器調試
支持遞歸(編譯器可能不展開)
支持模板和泛型
可以與?constexpr、template?配合
?缺點
? ? ? ? 內聯函數不建議聲明和定義分離,分離會導致連接錯誤。因為inline被展開了,就沒有了函數地址,鏈接就找不到了。
濫用內聯會導致代碼膨脹(code bloat),生成的可執行文件變大
只適合小函數,復雜函數不一定 inline,(代碼小于二十行,遞歸)。
編譯器有最終決定權,inline?是一種建議而非強制
四、對比總結表
特性 | 宏函數(C) | 內聯函數(C++) |
---|---|---|
執行階段 | 預處理階段 | 編譯階段 |
類型檢查 | ? 無類型檢查 | ? 有完整類型檢查 |
安全性 | ? 易出錯,有副作用 | ? 安全,作用域清晰 |
調試性 | ? 不可調試 | ? 可調試 |
遞歸支持 | ? 不支持 | ? 支持(是否內聯由編譯器決定) |
用法建議 | 僅限非常簡單的表達式 | 小函數、頻繁調用的函數 |
性能提升 | ?(暴力替換) | ?(編譯器優化,智能展開) |
五、【面試題】
1、宏的優缺點?
2、c++有哪些技術可以替代宏?
????????利用常量定義換用const。
#define N 10;
替換為
const int N = 10;
????????短小函數定義,換用內聯函數。
宏函數定義 -->利用內聯函數替換
#define MAX(a, b) ((a) > (b) ? (a) : (b))// 改寫為模板內聯函數 template <typename T> inline T Max(T a, T b) {return a > b ? a : b; }
3、?宏和內聯函數的本質區別是什么?
考點:?宏是預處理階段處理,內聯函數是編譯階段處理。
宏在預處理階段展開,是文本替換,沒有類型檢查,也沒有作用域限制;
內聯函數是真正的函數,有類型檢查,有作用域,并能調試。
4、?什么是內聯函數?什么時候應該使用?
考點:?減少函數調用開銷,提升性能。
適合小函數,如 getters/setters;
不建議用于遞歸、虛函數、復雜函數;
編譯器可以不采納?inline?建議(非強制)。
5、內聯函數會影響編譯時間和可執行文件大小嗎?
考點:
增加編譯時間(編譯器要展開多次);
增加可執行文件大小(代碼膨脹);
但可以減少函數調用棧的開銷。
好了本期有關于c語言宏定義知識回顧和c++內聯函數的分享就到這里結束了,謝謝大家的支持和點贊收藏👍。