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

简介: # 工欲善其事必先利其器-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语言自我修养——从芯片、编译器到操作系统》

目录
相关文章
|
28天前
|
机器学习/深度学习 算法 数据挖掘
C语言在机器学习中的应用及其重要性。C语言以其高效性、灵活性和可移植性,适合开发高性能的机器学习算法,尤其在底层算法实现、嵌入式系统和高性能计算中表现突出
本文探讨了C语言在机器学习中的应用及其重要性。C语言以其高效性、灵活性和可移植性,适合开发高性能的机器学习算法,尤其在底层算法实现、嵌入式系统和高性能计算中表现突出。文章还介绍了C语言在知名机器学习库中的作用,以及与Python等语言结合使用的案例,展望了其未来发展的挑战与机遇。
45 1
|
28天前
|
人工智能 安全 算法
基于C语言的嵌入式系统开发,涵盖嵌入式系统概述、C语言的优势、开发流程、关键技术、应用实例及面临的挑战与未来趋势。
本文深入探讨了基于C语言的嵌入式系统开发,涵盖嵌入式系统概述、C语言的优势、开发流程、关键技术、应用实例及面临的挑战与未来趋势。C语言因其高效、可移植、灵活及成熟度高等特点,在嵌入式系统开发中占据重要地位。文章还介绍了从系统需求分析到部署维护的完整开发流程,以及中断处理、内存管理等关键技术,并展望了嵌入式系统在物联网和人工智能领域的未来发展。
67 1
|
6月前
|
C语言
C语言实现2048小游戏---粤嵌GE6818嵌入式系统实训
C语言实现2048小游戏---粤嵌GE6818嵌入式系统实训
|
6月前
|
安全 Unix Linux
嵌入式C语言(十四)
嵌入式C语言(十四)
48 0
|
4月前
|
存储 缓存 编译器
【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)(下篇)
scanf处理⽤⼾输⼊的原理是,⽤⼾的输⼊先放⼊缓存,等到按下回⻋键后,按照占位符对缓存进⾏解读。 解读⽤⼾输⼊时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为⽌。
194 2
|
4月前
|
存储 C语言
【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)(上篇)
printf 的作⽤是将参数⽂本输出到屏幕。它名字⾥⾯的 f 代表 format (格式化),表⽰可以定制输出⽂本的格式。
94 1
|
4月前
|
算法 IDE 程序员
C语言与嵌入式系统:嵌入式C编程基础。
C语言与嵌入式系统:嵌入式C编程基础。
93 0
|
6月前
|
存储 移动开发 C语言
技术心得记录:嵌入式开发中常用到的C语言库函数
技术心得记录:嵌入式开发中常用到的C语言库函数
70 1
|
6月前
|
C语言
C语言实现电子音乐相册---粤嵌GEC6818嵌入式系统实训
C语言实现电子音乐相册---粤嵌GEC6818嵌入式系统实训
|
6月前
|
安全 Linux 编译器
嵌入式C语言(十二)
嵌入式C语言(十二)
42 1