C语言函数_内联函数

学习C语言的重要内容之一是学习函数调用的内部机制,下文将从函数调用开始引申到内联函数,直到剖析内联函数。


函数调用(假设函数A调用了函数B)

对于被调用者B而言,其执行过程分为5步:

  1. 若有形参,在栈上给形参变量开辟空间
  2. 若有形参,进行实参与形参的值传递
  3. 执行函数体
  4. return结束函数,若有返回值,把值返回到调用处
  5. 由系统释放该函数在栈上的空间

对于调用者A而言,因为函数结束后需要回到调用处继续执行下面的代码,因此需要保存调用前的现场以及恢复现场,于是A的调用过程分为3步:

  1. 保存现场,记录函数A当前的执行状态(包括一些寄存器的值、执行地址等等)
  2. B函数的执行
  3. 恢复现场,取出保护现场时的记录,重新回到调用B前的状态

于是整个过程图解如下:
这里写图片描述
在图中我们看到了保存现场和恢复现场都是需要一定时间的,这两个时间之和就是调用一个函数的切换时间,而切换时间大概有多长呢?我们可以测试一下,本人的编译环境是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函数的切换时间比函数执行代码时间要长,而真正对工程作出贡献的是函数执行代码那部分,当需要成千上万次切换的时候,实际执行效率显得相当低下,图解如下:
这里写图片描述

那么怎么去解决这个问题?这就是内联函数出现的必要,上述问题的根本原因是函数调用所开销的切换时间,如果没有函数调用,而是直接把函数体拷贝到调用处,这样就不需要函数的切换了,这就是内联函数的含义,图解如下:
这里写图片描述


内联函数

可以使用inline 定义一个内联函数,如下:

static inline int max(int a, int b)
{
	return ((a>b)?a:b);
}

使用内联函数是以空间换时间的一种概念,若函数A多处调用内联函数B,则A的实际运行代码会被B的副本所加入而展开,程序尺寸会增大。
综上所述,如果函数B满足以下2个条件,就可以考虑把它设计为内联函数:

  1. **函数的代码简短。**简短到其执行时间和切换时间具有可比性,比如内部只有3-4条语句。因为如果函数复杂,函数体执行时间较长,那么节约函数的切换时间显得非常可笑,并且此时大量的代码被重复拷贝,更加得不偿失。
  2. **函数被频繁调用。**如果一个函数的使用率很低,那么对它的优一切化都是徒劳的,在大型项目中,真正需要优化的都是热点代码,能够明显显示程序运行效率的代码。

最后需要说明,内联函数是C参考C++的,并不是所有编译器都会实现得一样,而且并不是所有编译器都会支持,很多编译器或许对inline函数没有处理,具体要查看编译器版本所支持的inline关键字操作。

猜你喜欢

转载自blog.csdn.net/Hxj_CSDN/article/details/80919091