C++基础——内联函数

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率

注意:内联只是将函数定义为内联,相当于给了编译器一种选项,编译器是否使用内联完全由编译器自己决定。一般而言,只要满足一定要求的函数才会使用内联 

我们通过查看反汇编的形式来观察内联函数和非内联函数在底层的实现,假定我们实现了一个加法函数Add(int int)如下所示

int Add(int left, int right)
{
    return left + right;
}

int main()
{
    int a = 10;
    int b = 20;

    int ret = Add(a, b);

    return 0;
}

此时未加inline关键字,使用g++编译这段代码并使用objdump查看反汇编得到汇编为

0000000000400521 <main>:
  400521:	55                   	push   %rbp             // %rbp入栈,保存上一个栈帧的栈底地址
  400522:	48 89 e5             	mov    %rsp,%rbp        // %rsp值给%rbp,%rbp保存当前栈帧栈底地址
  400525:	48 83 ec 10          	sub    $0x10,%rsp       // %rsp-0x10,为main函数开辟16个字节栈帧
  400529:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)  // 将10 保存在栈底-4地址处
  400530:	c7 45 f8 14 00 00 00 	movl   $0x14,-0x8(%rbp) // 将20 保存在占地-8位置处
  400537:	8b 55 f8             	mov    -0x8(%rbp),%edx  // 将20 保存到寄存器%edx内
  40053a:	8b 45 fc             	mov    -0x4(%rbp),%eax  // 将10 保存到寄存器%eax内
  40053d:	89 d6                	mov    %edx,%esi        // 将20 传递到寄存器%esi内作为第二个参数
  40053f:	89 c7                	mov    %eax,%edi        // 将10 传递到寄存器%edi内作为第一个参数
  400541:	e8 c7 ff ff ff       	callq  40050d <_Z3Addii>// call函数Add实现函数调用
  400546:	89 45 f4             	mov    %eax,-0xc(%rbp)  // 将%eax中的结果(由Add返回)保存到栈上(ret)
  400549:	b8 00 00 00 00       	mov    $0x0,%eax        // 将0传入%eax作为main函数返回值
  40054e:	c9                   	leaveq                  // 恢复%rsp和%rbp
  40054f:	c3                   	retq 
000000000040050d <_Z3Addii>:
  40050d:	55                   	push   %rbp            // 将main函数的栈底地址入栈
  40050e:	48 89 e5             	mov    %rsp,%rbp       // %rsp保存当前栈底
  400511:	89 7d fc             	mov    %edi,-0x4(%rbp) // 将参数一入栈
  400514:	89 75 f8             	mov    %esi,-0x8(%rbp) // 将参数二入栈
  400517:	8b 45 f8             	mov    -0x8(%rbp),%eax // 将参数二保存到寄存器%eax
  40051a:	8b 55 fc             	mov    -0x4(%rbp),%edx // 将参数一保存到寄存器%edx
  40051d:	01 d0                	add    %edx,%eax       // 完成 10 + 20,并将结果保存到%eax中
  40051f:	5d                   	pop    %rbp            // 从栈中弹出main函数栈底地址并保存到%rbp
  400520:	c3                   	retq                   // 从栈中弹出返回地址并返回main

可以看到,对于普通的函数调用,会使用call指令去调用位于40050d处的<_Z3Addii>,此时会为函数Add开辟栈帧,会带来一定的开销

接着看一下inline版本的Add函数,由于g++默认不使用内联,需要我们做一些处理

inline int Add(int left, int right) __attribute__((always_inline));

inline int Add(int left, int right)
{
    return left + right;
}

int main()
{
    int a = 10;
    int b = 20;

    int ret = Add(a, b);

    return 0;
}

将 __attribute__((always_inline))加在声明后,可以强制g++编译器使用内联

同样使用objdump获得反汇编如下,并且所得到的反汇编内<_Z3Addii>已经不存在了

000000000040064d <main>:
  40064d:	55                   	push   %rbp
  40064e:	48 89 e5             	mov    %rsp,%rbp
  400651:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)    // 10 保存到栈上(rbp-4)a
  400658:	c7 45 f8 14 00 00 00 	movl   $0x14,-0x8(%rbp)   // 20 保存到栈上(rbp-8)b
  40065f:	8b 45 fc             	mov    -0x4(%rbp),%eax    // 10 保存到寄存器%eax内
  400662:	89 45 f0             	mov    %eax,-0x10(%rbp)   // 将10保存到栈上(rbp-16)left
  400665:	8b 45 f8             	mov    -0x8(%rbp),%eax    // 20 保存到寄存器%eax内
  400668:	89 45 ec             	mov    %eax,-0x14(%rbp)   // 将20保存到栈上(rbp-20)right
  40066b:	8b 45 ec             	mov    -0x14(%rbp),%eax   // 将20保存到寄存器%eax内
  40066e:	8b 55 f0             	mov    -0x10(%rbp),%edx   // 将10保存到寄存器%edx内
  400671:	01 d0                	add    %edx,%eax          // 10 + 20,将结果保存到%eax
  400673:	89 45 f4             	mov    %eax,-0xc(%rbp)    // 将结果保存到栈上(rbp-12)ret
  400676:	b8 00 00 00 00       	mov    $0x0,%eax          // 将0传入%eax作为main函数返回值
  40067b:	5d                   	pop    %rbp               // 恢复%rbp
  40067c:	c3                   	retq 

使用内联函数之后,原先的函数调用的操作直接在当前函数的栈帧内进行,实际上在栈上为参数a、b创建了一份副本,整个的函数栈帧为

  •  inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率
  • inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
  • inline不建议声明和定义分离(不同文件),分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
  • 关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用

因此,在C语言中使用的宏函数,建议在C++中使用inline进行代替 

猜你喜欢

转载自blog.csdn.net/weixin_58165485/article/details/128576243