# 工欲善其事必先利其器-C语言拓展--嵌入式C语言(九)

工欲善其事必先利其器-C语言拓展–嵌入式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语言自我修养——从芯片、编译器到操作系统》

你可能感兴趣的:(C语言扩展-嵌入式C语言,c语言,开发语言,arm,物联网,linux)