C语言内联inline函数(C99)与普通函数在汇编代码视角下的区别

内联函数(C99)

C99引入一个新关键字inline,用于定义内联函数(inline function)。通常函数的调用都有一定的开销,因为函数的调用包括建立调用、传递参数、跳转到函数代码并返回。内联函数就是以空间换时间,使函数的调用更加快捷。
标准规定:具有内部链接的函数可以成为内联函数,还规定了内联函数的定义与调用该函数的代码必须在同一个文件中。因此最简单的办法是使用inlinestatic修饰。通常,内联函数应定义在首次使用它的文件中,所以内联函数也相当于函数原型。

关于内联函数的细节描述,可以参考博文:和宏一样快的内联函数( An Inline Function is As Fast As a Macro)

如下示例程序:

#include<stdio.h>

static inline int MAX(int a, int b)
{
	return a > b ? a : b;
}
int a[] = {19, 3, 5, 2, 1, 0, 8, 7, 6, 4 };
int max(int n)
{
	return n == 0 ? a[0] : MAX(a[n], max(n-1));
}
int main(void)
{
	printf("Max = %d\n",max(9));
	return 0;
}

该程序定义了一个内联函数MAX(),并定义了一个max()函数,通过递归以求出数组a中值最大的元素。此外,需要注意以下几点:

  1. 在日常使用内联函数时,并不建议对内联函数进行嵌套使用(递归),因为内联函数也是类似宏定义的字符串替换,嵌套使用很容易造成代码量增大和冗余。

  2. static 关键字不能省略,要将内联函数定义为 静态static函数,读者可以尝试将static关键字去除,

    并使用 gcc main.c -g 命令进行编译 。编译器会报出undefined reference to MAX的错误,关于此错误的解析可以参考博客:gcc编译inline函数报错:未定义的引用

对main.c进行编译后,然后反汇编:

$ gcc main.c -g
$ objdump -dS a.out

在关于MAX()和max()函数的汇编部分,可以看到:

static inline int MAX(int a, int b)
{
    1149:	55                   	push   %rbp
    114a:	48 89 e5             	mov    %rsp,%rbp
    114d:	89 7d fc             	mov    %edi,-0x4(%rbp)
    1150:	89 75 f8             	mov    %esi,-0x8(%rbp)
	return a > b ? a : b;
    1153:	8b 45 fc             	mov    -0x4(%rbp),%eax
    1156:	39 45 f8             	cmp    %eax,-0x8(%rbp)
    1159:	0f 4d 45 f8          	cmovge -0x8(%rbp),%eax
}
    115d:	5d                   	pop    %rbp
    115e:	c3                   	retq   


int max(int n)
{
    115f:	f3 0f 1e fa          	endbr64 
    1163:	55                   	push   %rbp
    1164:	48 89 e5             	mov    %rsp,%rbp
    1167:	48 83 ec 10          	sub    $0x10,%rsp
    116b:	89 7d fc             	mov    %edi,-0x4(%rbp)
	return n == 0 ? a[0] : MAX(a[n], max(n-1));
    116e:	83 7d fc 00          	cmpl   $0x0,-0x4(%rbp)
    1172:	75 08                	jne    117c <max+0x1d>
    1174:	8b 05 a6 2e 00 00    	mov    0x2ea6(%rip),%eax        # 4020 <a>
    117a:	eb 2f                	jmp    11ab <max+0x4c>
    117c:	8b 45 fc             	mov    -0x4(%rbp),%eax
    117f:	83 e8 01             	sub    $0x1,%eax
    1182:	89 c7                	mov    %eax,%edi
    1184:	e8 d6 ff ff ff       	callq  115f <max>
    1189:	89 c2                	mov    %eax,%edx
    118b:	8b 45 fc             	mov    -0x4(%rbp),%eax
    118e:	48 98                	cltq   
    1190:	48 8d 0c 85 00 00 00 	lea    0x0(,%rax,4),%rcx
    1197:	00 
    1198:	48 8d 05 81 2e 00 00 	lea    0x2e81(%rip),%rax        # 4020 <a>
    119f:	8b 04 01             	mov    (%rcx,%rax,1),%eax
    11a2:	89 d6                	mov    %edx,%esi
    11a4:	89 c7                	mov    %eax,%edi
    11a6:	e8 9e ff ff ff       	callq  1149 <MAX>
}
    11ab:	c9                   	leaveq 
    11ac:	c3                   	retq  

在倒数第四行可以看到MAX函数的调用call命令:11a6: e8 9e ff ff ff callq 1149 <MAX>,说明MAX()函数此时是作为普通函数使用的。

如果使用-O选项指定GCC进行优化编译,然后反汇编:

$ gcc main.c -g -O
$ objdump -dS a.out

可以看到max()函数的汇编代码,但是没有MAX()函数的汇编代码:

int max(int n)
{
    1149:	f3 0f 1e fa          	endbr64 
	return n == 0 ? a[0] : MAX(a[n], max(n-1));
    114d:	85 ff                	test   %edi,%edi
    114f:	75 07                	jne    1158 <max+0xf>
    1151:	8b 05 c9 2e 00 00    	mov    0x2ec9(%rip),%eax        # 4020 <a>
}
    1157:	c3                   	retq   
{
    1158:	53                   	push   %rbx
    1159:	89 fb                	mov    %edi,%ebx
	return n == 0 ? a[0] : MAX(a[n], max(n-1));
    115b:	8d 7f ff             	lea    -0x1(%rdi),%edi
    115e:	e8 e6 ff ff ff       	callq  1149 <max>
    1163:	48 8d 15 b6 2e 00 00 	lea    0x2eb6(%rip),%rdx        # 4020 <a>
    116a:	48 63 db             	movslq %ebx,%rbx
	return a > b ? a : b;
    116d:	39 04 9a             	cmp    %eax,(%rdx,%rbx,4)
    1170:	0f 4d 04 9a          	cmovge (%rdx,%rbx,4),%eax
}
    1174:	5b                   	pop    %rbx
    1175:	c3                   	retq   

可以看到,并没有call指令调用MAX函数, MAX函数的指令是内联在max函数中的,由于源代码和指令的次序无法对应, max和MAX函数的源代码也交错在一起显示。 因为MAX()函数已经嵌入到max()函数里面了,此时编译器把MAX()函数当作内联函数,所以没有给它分配单独的代码空间,所以也无法获得该内联函数的地址。

关于C语言的内联函数的使用总结,有如下几点:

  • 由于并未给内联函数预留单独的代码块,所以无法获得内联函数的地址,另外,内联函数无法在调试器中显示。
  • 由于内联函数具有内部链接,所以在多个文件中定义同一个内联函数不会产生什么影响。
  • 如果多个文件都需要使用同一个内联函数,可以将它定义在h头文件中。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C语言中,内联函数和宏定义都是用来提高程序的执行效率的方式,但它们之间有一些区别。 首先,内联函数是在函数调用时将函数体直接复制到调用处,而不是通过函数调用来执行代码。这样可以节省了函数调用的开销,提高了程序的运行速度。内联函数需要使用`inline`关键字来声明,并且适用于函数体较小的情况。内联函数有以下特点:只能在函数定义时使用`inline`关键字声明;内联函数的定义通常放在头文件中;对于大型的、递归的或包含循环的函数不宜使用内联函数。 其次,宏定义是一种文本替换的机制,通过在代码中定义宏,在程序编译前将宏名称替换为对应的宏内容。宏定义使用`#define`指令来定义,在宏替换时没有函数调用的开销,因此宏定义可以加速程序的执行。宏定义有以下特点:可以在任何地方进行定义和使用;使用宏定义时进行的是文本替换,不会创建函数的副本;替换时可能存在隐藏的错误。 总结起来,内联函数和宏定义在提高程序执行效率方面有相似的作用,但内联函数通常比宏定义更安全可靠。由于内联函数是使用函数调用方式执行代码,因此可以进行类型检查和其他编译期间的优化,而宏定义只是进行文本替换,所以容易出现一些潜在的错误。根据具体的代码需求和效率要求,选择使用内联函数或宏定义更加合适。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SOC罗三炮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值