汉扬编程 编程大纲 C语言中的Inline内联函数解析

C语言中的Inline内联函数解析

C语言中的Inline内联函数解析

C语言中的Inline内联函数解析

在C语言中,为了解决一些频繁调用的小函数大量消耗栈空间或是叫栈内存的问题,特别的引入了inline修饰符,表示为内联函数。

栈空间就是指放置程序的局部数据也就是函数内数据的内存空间。

那么在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程序出错的问题:比如函数的死循环递归调用的最终结果就是导致栈内存空间枯竭,程序崩溃。

下面我们来看一个例子

#include <stdio。h>

inline char* dbtest(int a); //函数原形声明为inline即:内联函数

int main( )

{

int i = 0;

for (i=1;i<=10;i )

{

printf(\”%d is %s/n\”,i,dbtest(i));

}

return 0;

}

char* dbtest(int a) //这里不用再次inline,当然加上inline也是不会出错的

{

return (a%2>0)?\”奇\”:\”偶\”;

}

上面的例子就是标准的内联函数的用法。

使用inline修饰带来的好处我们表面看不出来,其实在内部的工作就是在每个for循环的内部任何调用 dbtest(i)的地方都换成了(i%2>0)?\”奇\”:\”偶\”。

这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。

说到这里很多人可能会问,既然inline这么好,还不如把所谓的函数都声明成inline?

这个问题是要注意的,inline的使用是有所限制的,inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如while,switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。

同时inline函数仅仅是一个建议,对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为你的函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,你声明内联只是一个建议而已。

其次,因为内联函数要在调用点展开,所以编译器必须随处可见内联函数的定义,要不然,就成了非内联函数的调用了。

因此,inline函数有其优点和缺点,有些场合可用,有些场合就不一定适用了。

C语言中的普通函数,内联函数和函数式宏定义,到底有什么区别?

前几天,有粉丝(@学无止境攻城狮)在我的一篇介绍C语言宏定义的文章的评论区回复说:“希望写一个关于内联函数和普通函数的区别,内联函数和宏函数的区别。”

C语言中的Inline内联函数解析

粉丝的问题

C语言中的Inline内联函数解析

@学无止境攻城狮提到,对C语言中的内联函数、普通函数以及函数式宏定义的适用场景不够了解,也就是说在C语言程序开发中,“不清楚什么时候用函数式宏定义好,还是用内联函数,或者普通函数好”。

C语言中的Inline内联函数解析

可能读者也有这样的疑惑,所以本文讨论这几种“函数”的区别,在了解区别后,相信读者自然就明白在何种场景应该使用何种“函数”了。

C语言中的Inline内联函数解析

不清楚用函数式宏定义,内联函数,还是普通函数好

C语言中的Inline内联函数解析

内联(inline)函数 内联函数会在它被调用的位置上展开,这一点表现的和 define 宏定义是非常相似的。展开是指内联函数的C语言代码会在其被调用处展开,这么看来,内联函数的“调用”应该加上引号,因为系统在“调用”内联函数时,无需再在为被调用函数做申请栈帧和回收栈帧的工作,即少了普通函数的调用开销,C语言程序的效率会得到一定的提升。

C语言中的Inline内联函数解析

另外,将内联函数的代码展开后,C语言编译器会将其与调用者本身的代码放在一起优化,所以也有进一步优化C语言代码,提升效率的可能。

C语言中的Inline内联函数解析

不过,天下没有免费的午餐,C语言程序要实现内联函数的上述特性是要付出一定的代价的。普通函数只需要编译出一份,就可以被所有其他函数调用,而内联函数没有严格意义上的“调用”,它只是将自身的代码展开到被调用处的,这么做无疑会使整个C语言代码变长,也就意味着占用更多的内存空间,以及更多的指令缓存。

C语言中的Inline内联函数解析

如果滥用内联函数,cpu 的指令缓存肯定是不够用的

C语言中的Inline内联函数解析

显然,如果滥用内联函数,cpu 的指令缓存肯定是不够用的,这会导致 cpu 缓存命中率降低,反而可能会降低整个C语言程序的效率。因此,建议把那些对时间要求比较高,且C语言代码长度比较短的函数定义为内联函数。如果在C语言程序开发中的某个函数比较大,又会被反复调用,并且没有特别的时间限制,是不适合把它做成内联函数的。

C语言中的Inline内联函数解析

函数式宏定义 C语言中的函数式宏定义可以像函数那样接收参数,不过不能像函数那样提供参数的类型检查,这个特点在有些程序员看来是不安全的。但是,函数式宏定义不关心参数类型这个特点,有时候也会被利用起来,写出一些适用性更广的C语言代码,例如:

#define max(__a, __b) ( (a)>(b)?(a):(b) ) 上面这段C语言宏定义代码实现了一个 max() 方法,它接收两个参数,并返回较大的那个参数,max() 方法不关心参数的类型,因此 __a 和 __b 可以是 int 型的,也可以是 char 型或者 double 型以及其他数据类型的。

嵌入式时代-C语言学习

如果按照普通的函数来实现 max() 方法,程序员将不得不为每一种数据类型都实现对应的方法:

int int_max(int a, int b);char char_max(char a, char b);double double_max(double a, double b);// 等其他几种数据类型… 虽然在C语言程序中调用普通函数,传递的参数会得到类型检查,更安全一些,但是可以看出这样也要求程序员写出更多功能雷同的C语言代码。这其中的取舍,读者自己定夺。

不少C语言程序员认为,除非宏能够带来不可替代的便捷,否则应该尽量避免使用宏,如果希望提升效率,应该尽量使用更安全的内联函数。

C语言代码示例 关于C语言内联函数和函数式宏定义,其实我之前的文章有过更详细的讨论,读者可参考:

C语言陷阱与技巧第4节,inline函数提升程序效率,是有条件的

C语言陷阱与技巧第7节,“函数式宏定义”有漏洞,为何还要用它?

接下来将给出一段C语言代码示例,进一步讨论普通函数、内联函数,以及函数宏定义的区别,请看:

int n_add(int a, int b){ return a+b;} __attribute__((always_inline)) inline int i_add(int a, int b){ return a+b;}#define d_add(a, b) (a+b)int main(){ int a = 1, b = 2; int c = a+b; c = n_add(a, b); c = i_add(a, b); c = d_add(a, b); return 0;}C语言代码示例

上述C语言代码很简单,其实就是计算两个 int 变量的和,不过这一计算过程使用了 4 种方法:

直接使用 + 运算符计算:c = a+b;编写普通C语言函数,并调用:c=n_add(a,b);编写内联函数,并调用:c=i_add(a,b);编写函数式宏定义,并调用:c=d_add(a,b); 只看C语言代码是看不出什么分别的,要搞清楚这几种方法的区别需要深入到指令一层。在编译这段C语言代码之前,先来重点考察一下函数式宏定义。

C语言中的 define 宏定义在编译之前的预处理阶段就会被处理

相信读者应该明白,C语言中的 define 宏定义在编译之前的预处理阶段就会被处理,所以我们输入 gcc -E 命令查看预处理后的C语言代码:

# gcc -E t.c预处理后的C语言代码

可见,在编译之前的预处理阶段,宏 d_add() 已经不见了,它的C语言代码则被替换到被调用处了,这是它与函数的区别之一——根本不会有调用过程的开销。实际上,函数式宏定义的这个特性可以做到一些普通函数和内联函数无法做到的工作,具体实例可参考我之前的文章。

现在编译这段C语言代码,并查看其汇编代码:

# gcc -g t3.c# objdump -dS a.out汇编代码

显然,与直接使用 “+”运算符计算相比,调用普通函数的开销更大,效率更低。再来考察一下内联函数的汇编代码,请看:

内联函数没有像宏那样被展开

可见,虽然在预处理阶段,内联函数没有像宏那样被展开,但是在生成指令时,编译器将它的指令展开到调用处了。

读者可对比直接使用 “+”运算符计算的汇编代码,和调用内联函数的汇编代码,应该能够发现二者是等价的,也就是说“调用”内联函数实际上是没有调用(callq)过程的,它的开销和宏,和直接使用 “+”运算符计算是一样的,都低于普通函数,效率都更高一点。

小结 本文主要讨论了C语言程序开发中的普通函数、内联函数,以及函数式宏定义的区别和使用场景,并给出了一段具体的C语言代码示例,可以看出,C语言中的内联函数和宏都可以在一定程度上提升效率。

但是鉴于函数式宏定义无法方便的提供参数类型检查,除非不得已,否则是不建议使用函数式宏定义的。另外,内联函数也不可以滥用,只建议把那些对时间要求比较高,且C语言代码长度比较短的函数定义为内联函数,否则程序的效率反而可能会降低,原因文章已经分析。

点个赞再走吧

欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。#C语言# #程序员#

本文来自网络,不代表汉扬编程立场,转载请注明出处:http://www.hyzlch.com/mianfei/6716.html

一文彻底搞懂设计模式(含C++实例代码)

以现在的眼光来看,Windows 95是怎样的操作系统?

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

返回顶部