嵌入式C语言(九)

简介: 嵌入式C语言(九)

内联函数

这个万一就是真的有点意思了,来来接着看看

这一节,我们接着介绍与内联函数相关的两个属性:noinline和always_inline。这两个属性的用途是告诉编译器,在编译时,对我们指定的函数内联展开或不展开。

static inline __attribute__((noinline)) int func();
static inline __attribute__((always_inline)) int func();

属性声明:noinline

一个使用inline声明的函数被称为内联函数,内联函数一般前面会有static和extern修饰。

使用inline声明一个内联函数,和使用关键字register声明一个寄存器变量一样,只是建议编译器在编译时内联展开

**使用关键字register修饰一个变量,只是建议编译器在为变量分配存储空间时,将这个变量放到寄存器里,**这会使程序的运行效率更高。

当然以上都是建议,至于会不会这样做,视具体情况而定,编译器要根据寄存器资源是否紧张、这个变量的类型及是否频繁使用来做权衡。

使用inline时也是一样,编译器也会根据实际情况,如函数体大小、函数体内是否有循环结构、是否有指针、是否有递归、函数调用是否频繁来做决定。

如GCC编译器,一般是不会对函数做内联展开的,只有当编译优化等级开到-O2以上时,才会考虑是否内联展开。

但是在我们使用noinline和always_inline对一个内联函数作显式属性声明后,编译器的编译行为就变得确定了:使用noinline声明,就是告诉编译器不要展开;使用always_inline属性声明,就是告诉编译器要内联展开。

那说了这么多,什么时内联函数欸?

在说这个事情之前,你得先明白个事情,函数在执行得过程中去调用其他的函数时是有很大得开销的。

看看调用过程:

(1)保存当前函数现场。

(2)跳到调用函数执行。

(3)恢复当前函数现场。

(4)继续执行当前函数。

需要不断地保存现场、恢复现场,这就是函数调用带来的开销。

对于一般的函数来说这个事情也就那么回事,但是如果你整个只有一句的短小精悍的代码,这样反复调用很不划算啊。

于是我们将这样的函数设置为内联函数,像宏一样在调用出直接展开执行,这样就可以减少函数调用的开销。

内联函数与宏对比

那为啥不直接整个宏算了?

来嘛来看看内联函数独特的优势:

● 参数类型检查:内联函数虽然具有宏的展开特性,但其本质仍是函数,在编译过程中,编译器仍可以对其进行参数检查,而宏不具备这个功能。

● 便于调试:函数支持的调试功能有断点、单步等,内联函数同样支持。

● 返回值:内联函数有返回值,返回一个结果给调用者。这个优势是相对于ANSI C说的,因为现在宏也可以有返回值和类型了,如前面使用语句表达式定义的宏。

● 接口封装:有些内联函数可以用来封装一个接口,而宏不具备这个特性。

看来还是很具有优势的,那我们看看编译器对内联函数的处理

编译器对内联函数的处理

虽然可以通过inline关键字将一个函数声明为内联函数,但编译器不一定会对这个函数做内联展开。编译器也要根据实际情况进行评估,**权衡展开和不展开的利弊,**并最终决定要不要展开。

内联函数并不是完美无瑕的,也有一些缺点。

内联函数会增大程序的体积,如果在一个文件中多次调用内联函数,多次展开,那么整个程序的体积就会变大,在一定程度上会降低程序的执行效率。同时内联函数往往降低了函数的复用性。

编译器在对内联函数做展开时,除了检测用户定义的内联函数内部是否有指针、循环、递归,还会在函数执行效率和函数调用开销之间进行权衡。

考虑因素:

● 函数体积小。

● 函数体内无指针赋值、递归、循环等语句。

● 调用频繁。

当我们认为一个函数体积小,而且被大量频繁调用,应该做内联展开时,就可以使用static inline关键字修饰它。但编译器不一定会做内联展开,如果你想明确告诉编译器一定要展开,或者不展开,就可以使用noinline或always_inline对函数做一个属性声明。

内联函数为什么定义在头文件中

如果你曾经看过这个玩意,你会发现其很多的内联函数都在头文件中,为什么?还非得用个static修饰一下。

因为它是一个内联函数,可以像宏一样使用,任何想使用这个内联函数的源文件,都不必亲自再去定义一遍,直接包含这个头文件,即可像宏一样使用。

使用inline定义的内联函数,编译器不一定会内联展开,那么当一个工程中多个文件都包含这个内联函数的定义时,编译时就有可能报重定义错误。而使用static关键字修饰,则可以将这个函数的作用域限制在各自的文件内,避免重定义错误的发生。

(static 修饰了那这个外面的函数不久用不成了?包含头文件不就可以了,static限制了使用的范文,定义在头文件,可以让其他文件包含。)

  • 【学习资料】《嵌入式C语言自我修养——从芯片、编译器到操作系统》
目录
相关文章
|
2月前
|
存储 编解码 编译器
嵌入式C语言(四)
嵌入式C语言(四)
28 0
|
2月前
|
存储 编译器 Linux
嵌入式C语言(三)
嵌入式C语言(三)
28 0
|
2月前
|
C语言
嵌入式C语言中的工具代码助你一臂之力
嵌入式C语言中的工具代码助你一臂之力
22 0
|
2月前
|
算法 项目管理 C语言
嵌入式 C 语言大神的进阶之路
嵌入式 C 语言大神的进阶之路
27 0
|
2月前
|
编译器 C语言
嵌入式C语言变量、数组、指针初始化的多种操作
嵌入式C语言变量、数组、指针初始化的多种操作
32 0
|
1月前
|
编译器 Linux C语言
嵌入式C语言(八)
嵌入式C语言(八)
20 0
|
1月前
|
存储 编译器 C语言
嵌入式C语言(六)
嵌入式C语言(六)
22 0
|
1月前
|
存储 编译器 程序员
嵌入式C语言(七)
嵌入式C语言(七)
19 0
|
1月前
|
缓存 小程序 编译器
嵌入式C语言(十)
嵌入式C语言(十)
25 0
|
10天前
|
安全 算法 开发工具
【C 言专栏】基于 C 语言的嵌入式系统开发
【5月更文挑战第1天】本文探讨了C语言在嵌入式系统开发中的核心作用。嵌入式系统作为专用计算机系统广泛应用于家电、汽车、医疗等领域,具备实时性、低功耗等特点。C语言因其高效性、可移植性和灵活性成为开发首选。文章介绍了开发流程,包括需求分析、硬件选型、软件设计至部署维护,并强调中断处理、内存管理等关键技术。C语言在智能家居、汽车电子和医疗设备等领域的应用实例展示了其广泛影响力。面对硬件限制、实时性要求和安全挑战,开发者需不断优化和适应新技术趋势,以推动嵌入式系统创新发展。
【C 言专栏】基于 C 语言的嵌入式系统开发