C++基础——内联函数-创新互联

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

我们提供的服务有:成都做网站、成都网站建设、微信公众号开发、网站优化、网站认证、京口ssl等。为上千企事业单位解决了网站和推广的问题。提供周到的售前咨询和贴心的售后服务,是有科学管理、有技术的京口网站制作公司

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

我们通过查看反汇编的形式来观察内联函数和非内联函数在底层的实现,假定我们实现了一个加法函数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
: 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
: 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被展开,就没有函数地址了,链接就会找不到

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

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


分享名称:C++基础——内联函数-创新互联
网页地址:http://scyanting.com/article/disocg.html