【C++】 6_内联函数分析

小南地带 2019-06-29

常量与宏

  • C++ 中的 const 常量可以替代宏常数定义

    • const int A; <==> #define A 3

C++ 中是否有解决方案替代宏代码片段呢?

内联函数

  • C++ 中推荐使用内联函数替代宏代码片段
  • C++ 中使用 inline 关键字声明内联函数
inline int func(int a, int b)
{
    return a < b ? a : b;
}

内联函数声明时 inline 关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求。

  • C++ 编译器可以将一个函数进行内联
  • 被 C++ 编译器内联编译的函数叫做内联函数
  • C++ 编译器直接将函数体插入到函数调用的地方
  • 内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)

C++ 编译器不一定满足函数的内联请求!

编程实验: 内联函数初探

#include <stdio.h>

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

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

void code_1()
{
    int a = 1;
    int b = 3;
    int c = FUNC(++a, b);
                             
    printf("\ncode_1: \n");
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("c = %d\n", c);
}

void code_2()
{
    int a = 1;
    int b = 3;
    int c = func(++a, b);
    
    printf("\ncode_2: \n");    
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("c = %d\n", c);
}

int main(int arc, char* argv[])
{
    code_1();
    code_2();

    return 0;
}
输出:
code_1: 
a = 3
b = 3
c = 3

code_2: 
a = 2
b = 3
c = 2

分析: 【宏无法避免的副作用】
int c = FUNC(++a, b);
==>
int  = ((++a) < (b) ? (++a) : (b))
  • 内联函数具有普通函数的特征(参数检查,返回类型等)
  • 函数的内联请求可能被编译器拒绝
  • 函数被内联编译后,函数体直接扩展到调用的地方

宏代码片段由预处理器处理,进行简单的文本替换,没有任何编译过程,因此可能出现副作用。

  • 现代 C++ 编译器能够进行编译优化,一些函数即使没有 inline 声明,也可能被内联编译
  • 一些现代 C++ 编译器提供了扩展语法,能够对函数进行强制内联(这降低了可移植行)

    • g++: __attribute__((always_inline))
    • MSVC: __foreinline

编程实验: 内联函数深度示例

#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 n;
}

内联函数扩展选项默认,未强制内联时
VC2010 汇编:【内联请求失败】

int r = add_inline(10);
010E13AE  push        0Ah  
010E13B0  call        add_inline (10E104Bh)   ; 发生函数调用
010E13B5  add         esp,4  
010E13B8  mov         dword ptr [r],eax

g++ 汇编 【内联请求失败】

main(int, char**):
08048494:   push %ebp
08048495:   mov %esp,%ebp
08048497:   and $0xfffffff0,%esp
0804849a:   sub $0x20,%esp
0804849d:   movl $0xa,(%esp)
080484a4:   call 0x80484c8 <add_inline(int)>  ; 发生函数调用
080484a9:   mov %eax,0x1c(%esp)
080484ad:   mov 0x1c(%esp),%eax
080484b1:   mov %eax,0x4(%esp)
080484b5:   movl $0x80485c0,(%esp)
080484bc:   call 0x80483c0 <printf@plt>
080484c1:   mov $0x0,%eax
080484c6:   leave 
080484c7:   ret

内联函数扩展选项默认,强制内联时
VC2010 汇编:【内联请求成功】

int r = add_inline(10);
008C102C  mov         dword ptr [ebp-8],0  
008C1033  mov         dword ptr [ebp-0Ch],0  
008C103A  jmp         wmain+35h (8C1045h)  
008C103C  mov         eax,dword ptr [ebp-0Ch]  
008C103F  add         eax,1  
008C1042  mov         dword ptr [ebp-0Ch],eax  
008C1045  cmp         dword ptr [ebp-0Ch],0Ah  
008C1049  jge         wmain+46h (8C1056h)  
008C104B  mov         ecx,dword ptr [ebp-8]  
008C104E  add         ecx,dword ptr [ebp-0Ch]  
008C1051  mov         dword ptr [ebp-8],ecx  
008C1054  jmp         wmain+2Ch (8C103Ch)  
008C1056  mov         edx,dword ptr [ebp-8]  
008C1059  mov         dword ptr [r],edx

g++ 汇编 【内联请求成功】

main(int, char**):
08048494:   push %ebp
08048495:   mov %esp,%ebp
08048497:   and $0xfffffff0,%esp
0804849a:   sub $0x20,%esp
 9        {
0804849d:   movl $0xa,0x18(%esp)
19            int ret = 0;
080484a5:   movl $0x0,0x14(%esp)
21            for(int i=0; i<n; i++)
080484ad:   movl $0x0,0x10(%esp)
080484b5:   jmp 0x80484c4 <main(int, char**)+48>
23                ret += i;
080484b7:   mov 0x10(%esp),%eax
080484bb:   add %eax,0x14(%esp)
21            for(int i=0; i<n; i++)
080484bf:   addl $0x1,0x10(%esp)
080484c4:   mov 0x18(%esp),%eax
080484c8:   cmp 0x10(%esp),%eax
080484cc:   setg %al
080484cf:   test %al,%al
080484d1:   jne 0x80484b7 <main(int, char**)+35>
26            return ret;
080484d3:   mov 0x14(%esp),%eax
10            int r = add_inline(10);
080484d7:   mov %eax,0x1c(%esp)
12            printf(" r = %d\n", r);
080484db:   mov 0x1c(%esp),%eax
080484df:   mov %eax,0x4(%esp)
080484e3:   movl $0x80485c0,(%esp)
080484ea:   call 0x80483c0 <printf@plt>
14            return 0;
080484ef:   mov $0x0,%eax
15        }

注意事项

  • C++ 中 inline 内联编译的限制(现在编译器基本都可满足内联要求,以下针对旧编译器而言)

    • 不能存在任何形式的循环语句
    • 不能存在过多的条件判断语句
    • 函数体不能过于复杂
    • 不能对函数进行取址操作
    • 函数内联声明必须在调用语句之前

小结

  • C++ 中可以通过 inline 声明内联函数
  • 编译器直接将内联函数扩展到函数调用的地方
  • inline 只是一种请求,编译器不一定允许这种请求
  • 内联函数省去了函数调用时压栈,跳转和返回的开销

以上内容参考狄泰软件学院系列课程,请大家保护原创!

相关推荐