学习C语言的重要内容之一是学习函数调用的内部机制,下文将从函数调用开始引申到内联函数,直到剖析内联函数。
函数调用(假设函数A调用了函数B)
对于被调用者B而言,其执行过程分为5步:
- 若有形参,在栈上给形参变量开辟空间
- 若有形参,进行实参与形参的值传递
- 执行函数体
- return结束函数,若有返回值,把值返回到调用处
- 由系统释放该函数在栈上的空间
对于调用者A而言,因为函数结束后需要回到调用处继续执行下面的代码,因此需要保存调用前的现场以及恢复现场,于是A的调用过程分为3步:
- 保存现场,记录函数A当前的执行状态(包括一些寄存器的值、执行地址等等)
- B函数的执行
- 恢复现场,取出保护现场时的记录,重新回到调用B前的状态
于是整个过程图解如下:
![](https://i-blog.csdnimg.cn/blog_migrate/e6788fa912f997fc6bbcdacf7a6bd31b.png)
在图中我们看到了保存现场和恢复现场都是需要一定时间的,这两个时间之和就是调用一个函数的切换时间,而切换时间大概有多长呢?我们可以测试一下,本人的编译环境是win10_64位操作系统,16.04_64位的Ubuntu虚拟机,gcc_version_5.4.0编译链。
切换时间
为了大概测试切换时间,我们需要把上图中B的函数体尽可能减少到无,于是我们设计一个“空函数”,函数体不作任何操作而直接return返回,并且记录函数调用前后的时间,这样来大概计算得出ticks数(没有转换为实际的秒或毫秒,只需要比较即可),为了增加说服力,这个过程执行1百万次,然后求均值,代码如下:
#include <time.h>
#include <stdio.h>
#define TEST_NUM 1000000
void Max(int a, int b)
{
//直接返回
return;
}
int main(void)
{
//clock_t是clock()函数返回的变量类型
clock_t start = 0L;
clock_t stop = 0L;
clock_t ret = 0L;
int i;
for(i = 0; i<TEST_NUM; ++i)
{
//记录ticks
start = clock();
Max(1,2);
stop = clock();
//计算
ret += (stop-start);
}
//输出均值
printf("ret_avg %lf ticks\n",(double)ret/(TEST_NUM*1.0));
}
小白的输出结果是:
ret_avg 0.481435 ticks
结果每次都会不一样,视CPU当前的工作量、操作系统进程数量等等情况而定,但是大概能够知道,在这样的环境下,切换时间约为0.5 ticks。那么0.5 ticks是什么样的时间长度概念??我们不需要转换为秒/毫秒,下面把函数调用部分换成语句,也就是直接实现Max的功能,代码如下:
#include <time.h>
#include <stdio.h>
#define TEST_NUM 1000000
void Max(int a, int b)
{
//直接返回
return;
}
int main(void)
{
//clock_t是clock()函数返回的变量类型
clock_t start = 0L;
clock_t stop = 0L;
clock_t ret = 0L;
int i;
for(i = 0; i<TEST_NUM; ++i)
{
//记录ticks
start = clock();
int a = 1;
int b = 2;
int c = a>b ? a: b;
stop = clock();
//计算
ret += (stop-start);
}
//输出均值
printf("ret_avg %lf ticks\n",(double)ret/(TEST_NUM*1.0));
}
运行结果与函数调用相比:
不调用函数:rret_avg 0.475625 ticks
调用函数:ret_avg 0.481435 ticks
大家可以测试更多的数据,使得数据更加有说服力。在此能够看到,函数的切换时间和若干条语句的执行的时候大概相等。
简短函数被频繁调用所产生的问题
由上述结果可知,若干条语句的执行时间≈函数切换时间,现在我们修改Max函数的代码,它用于求两个整型数据之间的最大者:
int Max(int a, int b)
{
return a>b ? a : b;
}
很明显,Max函数中只有一句执行代码,大概比切换时间还要短。
现在的背景是,Max()这样一个简短的函数在一个工程中被频繁调用,甚至出现了成千上万次调用,那么有一个非常令人无奈的情况出现:Max函数的切换时间比函数执行代码时间要长,而真正对工程作出贡献的是函数执行代码那部分,当需要成千上万次切换的时候,实际执行效率显得相当低下,图解如下:
![](https://i-blog.csdnimg.cn/blog_migrate/21fcf1292680cf070c1f4b449849f2c8.png)
那么怎么去解决这个问题?这就是内联函数出现的必要,上述问题的根本原因是函数调用所开销的切换时间,如果没有函数调用,而是直接把函数体拷贝到调用处,这样就不需要函数的切换了,这就是内联函数的含义,图解如下:
![](https://i-blog.csdnimg.cn/blog_migrate/bb85754a3e76205738fde1fecc1545c2.png)
内联函数
可以使用inline 定义一个内联函数,如下:
static inline int max(int a, int b)
{
return ((a>b)?a:b);
}
使用内联函数是以空间换时间的一种概念,若函数A多处调用内联函数B,则A的实际运行代码会被B的副本所加入而展开,程序尺寸会增大。
综上所述,如果函数B满足以下2个条件,就可以考虑把它设计为内联函数:
- 函数的代码简短: 简短到其执行时间和切换时间具有可比性,比如内部只有3-4条语句。因为如果函数复杂,函数体执行时间较长,那么节约函数的切换时间显得非常可笑,并且此时大量的代码被重复拷贝,更加得不偿失。
- 函数被频繁调用: 如果一个函数的使用率很低,那么对它的优一切化都是徒劳的,在大型项目中,真正需要优化的都是热点代码,能够明显显示程序运行效率的代码。
最后需要说明,内联函数是C参考C++的,并不是所有编译器都会实现得一样,而且并不是所有编译器都会支持,很多编译器或许对inline函数没有处理,具体要查看编译器版本所支持的inline关键字操作。