C++ 內聯函數分析

一、常量與宏

C++中的const常量能夠替代宏常數定義:c++

#define A 3
const int A = 3;

咱們還能夠利用宏來定義宏代碼片斷:函數

#define FUNC(a, b) ((a) < (b) ? (a) : (b))

可是宏代碼塊不是函數, 常帶有反作用,爲消除反作用,用函數來替代,可是函數在調用的是由有參數的入棧,函數的返回,棧變量的銷燬等等內存的開銷,宏代碼塊則是徹底沒有的,爲綜合二者的優勢,C++出現了內聯函數。優化

宏代碼塊是由預處理處理,進行簡單的文本替換,沒有通過任何編譯過程,所以可能出現反作用,而內聯函數則是在編譯階段進行處理,具備函數的特徵(參數檢查、返回類型等)。spa

因爲宏使用簡單的文本替換,對於有些狀況,在同一個做用域中同一個宏使用兩次會出現重定義錯誤。code

#define SWAP(a,b)\  
    int tmp = a; \  
    a = b; \  
    b = tmp;  
int main()  
{  
    int x = 10;  
    int y = 5;  
    SWAP(x, y);  
    SWAP(x, y);//此處會出錯  
    system("pause");  
    return 0;  
}

二、內聯函數

C++中推薦使用內聯函數替代宏代碼片斷,基本形式以下:圖片

inline int func(int a, int b)
{
    return a < b ? a : b;
}
  • C++編譯器能夠將一個函數進行內聯編譯
  • 被C++編譯器內聯編譯的函數叫作內聯函數
  • C++編譯器直接將函數體插入函數調用的地方
  • 內聯函數具備普通函數的特徵(參數檢查、返回類型等)
  • 內聯函數是以空間換時間的作法,沒有普通函數調用時的額外開銷
  • 函數被內聯編譯後,函數體直接擴展到調用的地方
  • C++編譯器不必定知足函數的內聯請求

inline關鍵字聲明能夠將一個函數聲明爲內聯函數,可是這種聲明不是絕對的,inline是對編譯器的一種請求,請求編譯器將對應函數進行內聯編譯,編譯器是能夠拒絕的,是否能夠內聯成功,要看編譯器。內存

inline內聯函數聲明時,inline關鍵字必需要和函數定義結合在一塊兒,僅將內聯放在聲明前是不起做用的。作用域

#include <stdio.h>

// 宏代碼塊,比較兩個數的大小
#define FUNC(a, b) ((a) < (b) ? (a) : (b))

inline int func(int a, int b)
{
    return a < b ? a : b;
}

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 3;
    int c = FUNC(++a, b);
    // ((++a) < (b) ? (++a) : (b))
    // 2 < 3 ? 3 : 3
    // 宏代碼塊的缺陷
    
    int d = func(++a, b);
    
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("c = %d\n", c);
    printf("d = %d\n", d);
    
    return 0;
}

查看反彙編進行分析編譯器

inline int func(int a, int b)    ; func函數在這裏被編譯
{
00EA17B0  push        ebp  
00EA17B1  mov         ebp,esp  
00EA17B3  sub         esp,0C4h  
00EA17B9  push        ebx  
00EA17BA  push        esi  
00EA17BB  push        edi  
00EA17BC  lea         edi,[ebp-0C4h]  
00EA17C2  mov         ecx,31h  
00EA17C7  mov         eax,0CCCCCCCCh  
00EA17CC  rep stos    dword ptr es:[edi]  
    return a < b ? a : b;
00EA17CE  mov         eax,dword ptr [a]  
00EA17D1  cmp         eax,dword ptr [b]  
00EA17D4  jge         func+31h (0EA17E1h)  
00EA17D6  mov         ecx,dword ptr [a]  
00EA17D9  mov         dword ptr [ebp-0C4h],ecx  
00EA17DF  jmp         func+3Ah (0EA17EAh)  
00EA17E1  mov         edx,dword ptr [b]  
00EA17E4  mov         dword ptr [ebp-0C4h],edx  
00EA17EA  mov         eax,dword ptr [ebp-0C4h]  
}    

...
...
...

    int d = func(++a, b); 調用函數
01001873  mov         eax,dword ptr [a]  
01001876  add         eax,1  
01001879  mov         dword ptr [a],eax  
0100187C  mov         ecx,dword ptr [b]  
0100187F  push        ecx  
01001880  mov         edx,dword ptr [a]  
01001883  push        edx  

01001884  call        func (01001177h)   ; 這裏就是func的函數調用

01001889  add         esp,8  
0100188C  mov         dword ptr [d],eax

發現編譯器並無內聯成功,仍是普通的函數調用it

對編譯器進行設置

圖片描述

再看反彙編代碼

inline int func(int a, int b)    ; 內聯函數在這裏不會被編譯
{
    return a < b ? a : b;
}
...
...
...

    int d = func(++a, b)    ; 函數調用的時候,直接將整個函數體插入調用的地方進行編譯
00BC4EC3  mov         eax,dword ptr [a]  
00BC4EC6  add         eax,1  
00BC4EC9  mov         dword ptr [a],eax  
00BC4ECC  mov         ecx,dword ptr [a]  
00BC4ECF  cmp         ecx,dword ptr [b]  
00BC4ED2  jge         main+7Fh (0BC4EDFh)  
00BC4ED4  mov         edx,dword ptr [a]  
00BC4ED7  mov         dword ptr [ebp-0F4h],edx  
00BC4EDD  jmp         main+88h (0BC4EE8h) 
00BC4EDF  mov         eax,dword ptr [b]  
00BC4EE2  mov         dword ptr [ebp-0F4h],eax  
00BC4EE8  mov         ecx,dword ptr [ebp-0F4h]  
00BC4EEE  mov         dword ptr [d],ecx

沒有了函數調用的代碼,直接是將內聯函數塊編譯進來了,省去了函數調用的開銷,內聯請求成功。

在Linux系統中用g++編譯器編譯,也是同樣的狀況,能夠經過配置g++ 編譯器達到內聯效果

  • 現代C++編譯器可以進行編譯優化,一些函數集是沒有inline聲明,也可能被內聯編譯
  • 一些現代C++編譯器提供了擴展語法,可以對函數進行強制內聯,如:

    g++ : __attribute__((always_inline)) 
    MSVS: __forceinline

強制內聯

#include <stdio.h>

//__forceinline
//__attribute__((always_inline))
inline 
int add_inline(int n);

int main(int argc, char *argv[])
{
    int r = add_inline(10);

    printf(" r = %d\n", r);

    return 0;
}

inline int add_inline(int n)
{
    int ret = 0;

    for(int i=0; i<n; i++)
    {
        ret += i;
    }

    return ret;
}

注意C++中inline內聯編譯的限制:

  • 不能存在任何形式的循環語句
  • 不能存在過多的條件判斷語句
  • 函數體不能過於龐大
  • 不能對函數進行取址操做
  • 函數內聯聲明必須在調用語句以前

三、小結

C++中能夠經過 inline聲明內聯函數

編譯器直接將內聯函數體擴展到函數調用的地方

inline只是一種請求,編譯器不必定容許這種請求

內聯函數省去了函數調用時壓棧、跳轉和返回等開銷

相關文章
相關標籤/搜索