内联函数
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内联函数相关的知识,希望对你有一定的参考价值。
麻烦给我解个疑:我今天看书,有句话这么说:内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处.我想问:那么一般函数是怎么调用的?
需详解,加分.
针对回答者 USSR F1的回答:printf()完之后释放栈,找到main的地址,接着执行下面的代码.我想问的是假如我把一个函数getX()申明为stastic,那又是怎么执行的?是不是不可以这样做?如果可以,那执行完getX()后,堆区对栈区的释放及找到main()又是怎么一回事?
当程序运行到调用函数的地方,比如main函数中有一处函数调用a=printf(…),首先把main里面当前这段代码的物理地址保存起来,放到为printf()准备的栈里面去,叫做“函数调用现场保护”。然后系统找到内存中printf()的入口地址,把实参的值按顺序传进来一一放到指定的位置上(形参的排列是有一定规律的)。此后系统就离开main,而开始去执行printf()去啦,也就是你说的“控制转移”。
当printf()函数执行完,返回的时候开始把栈里面的内容依次取出来,别忘了他是先进后出的,也就是说最后取到原来的那个物理地址,于是系统又回到main的那个地方,然后把最近取出来的另一个变量的值送回到main的位置,比如这里由a接收。同时把为printf()建立的栈销毁掉,printf()的那些形参和局部变量就统统寿终正寝了。一个函数最多只能返回一个值,因为更早取出来的值都被抛弃掉啦。
这种函数调用,因为要建栈、保护调用现场,转移物理地址,执行完了后还要再出栈、返回地址、返回局部变量值,挺耗时间的。
补充:程序运行期间所有函数的栈是使用一段空间的,用"栈顶"和"栈底"为每个函数标记自己的栈。新进栈的数据都放在栈顶,出栈的也是从栈顶开始出。而当栈顶和栈底重合,这个栈差不多可以销毁它了(把栈顶和栈底标记改掉,没法找到它了,也就算销毁了),不过我在数据结构里面和汇编学的栈略有不同。
今天看了一下VC++6.0编译出来的汇编码,它编译出来的程序大致是这样运行的:
调用一个普通的函数时,系统会把原来为main()所建栈的栈底地址进到栈里,以便返回时恢复main()的栈,接着把调用函数的实参进到栈里,把当前正在运行位置的物理地址(代码区的)也进到栈里,然后转到被调用函数的入口地址。这时的栈还暂时属于main()的。但马上会把这个栈顶提升为栈底(新栈底正好是刚才存放main栈顶地址的那个内存单元),新的栈只能向上发展,就把原来属于main的栈给屏蔽掉了。关于实参和形参结合的过程,我认为前面实参的进栈就是了,实参在栈中所占的内存单元就是形参的地址,函数中需要操作形参就直接去栈里面找到它们。
(PS:个别地方和我接触过的“标准”上说的有些矛盾,尤其是形参在栈底下面,是VC++的编译器对程序作了优化,减少了不必要的空间开销吗?也可能是我对栈的定义理解不是很准确,请见谅。)
当函数全部语句执行完要返回时,严格遵循相反的步骤,把栈顶调回栈底,再向下把刚才保存的那个main的栈底地址取出来恢复为栈底,这时候栈的形态已经恢复为原来main的了,然后栈顶继续下降,就把调用现场的物理地址也取出来了,根据这个地址,流程回到了main中发生本次调用的地方(位于代码区)。如果return语句返回的是一个数而不是变量名字,则这个数据不保存在内存中,而是保存在CPU的寄存器里面。系统去指定的位置把返回值取出来送给左侧接受它的变量,然后把栈顶恢复到函数调用前的位置,继续向后运行。
关于static关键字,它修饰变量的时候,系统会把这些变量放到内存中的静态存储区,而不是栈区。静态存储区的单元一旦分配了,在程序结束前就不会被释放,也就谈不上再重新分配给其他的变量了。但是楼主说的用来修饰函数,我感觉在VC++6.0下好像没有引起任何变化。严格说来被调用函数结束后它在栈中的数据还继续存在着,但是系统不再访问它们了,其实我觉得也可以访问,但是编译器不肯去为我们做而已。而且如果后面再发生一次函数调用,新的栈会紧挨着main生长,原来留下的那些数据就给覆盖掉了。
哦,我在我的机子上还发现了一个有趣的现象,函数的栈建立起来后,在正式开始执行函数的代码前编译器会用一个奇怪的数据0x0cccccccch来初始化所有的栈单元(32位机下4个字节为一组),不知道为什么这样做。如果程序中定义自动变量没有初始化,就会导致它们具有不可预料的值,但在VC++6.0下这样是可以预料它们的值的。楼主如果有兴趣可以把下面这段代码放到任意一个函数里试试
long a,b;if(a==b) printf("a=%ld,b=%ld",a,b); 参考技术A 和一般函数的调用一样,但是调用处要能看到内联函数的函数体,所以内联函数的函数体还一般函数不同,要放在头文件中 参考技术B 如果你指的内联是inline。
inline可以看作把一段代码直接复制到另一个函数中(调用一次复制一次)
一般函数是通过汇编/机器码的jump跳转到相应的地址继续执行,会使用一些cpu时钟,所以效率相对较低。
总的来说,inline是用空间换时间。一般函数反之。 参考技术C 一般的函数,是采用堆栈保存当前程序的状态和局部变量等,而后再转向被调用的函数去执行,当被调函数执行完后,再从堆栈中返回原状态和局部变量等,所以效率不高
而把函数设置为内联,编译器就不会把它当做一般函数来处理,而会当做类似于C语言中的含参宏使用,即:将该段代码在编译期间直接加入调用程序处,因此,在执行效率上,会较高,而且实现了模块化,并且比含参宏使用,安全性更高,但内联是有使用限制的,这我想你知道,我就不废话了
如还有什么没说清的,可以Hi我讨论 参考技术D 内联是inline在函数名前面标记,主要是使程序运行的速度快 一般短小的函数使用内联
内联函数和宏
对于内联函数:
(1)、在C++中,以inline修饰的函数叫做内联函数,编译时C++编译器会调用内联函数的地方展开,没有函数压栈开销,内联函数提升程序运行的效率。
(2)、内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。而宏自是一个简单的替换。
(3)、内联函数要做参考类型检查,这是内联函数跟宏相比的优势。
eg:#define swap(a,b){int temp=a;a=b;b=temp;}
(4)、inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。对于短小的代码来说,inline可以带来一定的效率提升,而且和C时代的宏函数相比,inline更安全可靠。可是这个是以增加空间消耗为代价。
inline一般用于以下情况:
1、一个函数被重复调用
2、函数只有简单的几行,且函数不包含for while swith 语句。
一般来说,只有在工程项目中,一个简单的函数需要被多次调用,则应该考虑inline。
对于宏函数:
(1)、宏在C语言里极其重要,而在C++里用得就少里。关于宏的第一规则是:绝不应该使用它,除非你不得不这样做。几乎每个宏都表明了程序设计里面的一个缺陷,因为它在编译器看到程序的正文之前重新摆布这些正文。
(2)、宏是在代码处不加任何验证的简单替补,而内联函数是将代码直接插入调用处,而减少了普通函数调用时的资源消耗。
(3)、宏不是函数,只是在编译前(编译预处理阶段)将程序中有关字符串替换成宏体。
(4)、inline函数是函数,但在编译中不单独产生代码,而是将有关代码嵌入到调用处。
inline int f(int i) { return i*i; } cout<<f(8);//调用执行cout<<8*8;
以上是关于内联函数的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin函数 ⑦ ( 内联函数 | Lambda 表达式弊端 | “ 内联 “ 机制避免内存开销 - 将使用 Lambda 表达式作为参数的函数定义为内联函数 | 内联函数本质 - 宏替换 )