C++函数注释二次调用注释位置

博客访问: 317217
博文数量: 145
博客积分: 0
博客等级: 民兵
技术积分: 1876
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: C/C++
& & & & &函数调用的过程实际上也就是一个中断的过程。在函数调用过程中,第一个进栈的是(主函数中的)调用处的下一条指令(即函数调用语句的下一条可执行语句)的地址;然后是函数的各个参数,而在大多数C/C++编译器中,在函数调用的过程中,函数的参数是由右向左入栈的;然后是函数内部的局部变量(注意static变量是不入栈的);在函数调用结束(函数运行结束)后,局部变量最先出栈,然后是参数,最后栈顶指针指向最开始存的指令地址,程序由该点继续运行。
& & & &那么C++中到底是怎样实现一个函数的调用的呢?参数入栈、函数跳转、保护现场、回复现场等又是怎样实现的呢?本文将对函数调用的过程进行深入的分析和详细解释,并在VC 6.0环境下进行演示。
&首先对三个常用的寄存器做一下说明,EIP是指令指针,即指向下一条即将执行的指令的地址;EBP为基址指针,常用来指向栈底;ESP为栈指针,常用来指向栈顶。
看下面这个简单的程序并在VC 6.0中查看并分析汇编代码
& & & & & & & & & & & & & & & &&&
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & &图1
2、函数调用
& & &&g_func()函数的汇编代码如下:
& & & & & & & & & & & & & & & & & & & &&
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &图2
&首先是三条push指令,分别将三个参数压入栈中,可以发现参数的压栈顺序是从右向左的。这时我们可以查看栈中的数据验证一下。如图3所示,从右边的实时寄存器表中我们可以看到ESP(栈顶指针)值为0x0012FEF0,然后从中间的内存表中找到内存地址0x0012FEF0处,我们可以看到内存中依次存储了0x(即参数1),0x(即参数2),0x(即参数3),即此时栈顶存储的是三个参数值,说明压栈成功。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & 图3& & & 
& &然后可以看到call指令跳转到地址0x,那么该地址处是什么呢?我们继续跟踪一下,在图4中我们看到这里又是一条跳转指令,跳转到0x0040CC80。我们再看一下地址0x0040CC80处,在图5中可以看到这才是真正的g_func函数,0x0040CC80是该函数的起始地址,这样就实现了到g_func函数的跳转。
& & & & & & & & & & & & & & & &
& & & & & & & & & & & & & & & & & & & & & & & &图4
& & & & & & & & & & & & & &&
& & & & & & & & & & & & & & & & & & & & & &图 5
3、 保存现场&&&&&&&&&&
  此时我们再来查看一下栈中的数据,如图6所示,此时的ESP(栈顶)值为0x0012FEEC,在内存表中我们可以看到栈顶存放的是0x0040CCE3,下面还是前面压栈的参数1,2,3,也就是执行了call指令后,系统默认的往栈中压入了一个数据(0x0040CCE3),那么它究竟是什么呢?我们再看到图3,call指令后面一条指令的地址就是0x0040CCE3,实际上就是函数调用结束后需要继续执行的指令地址,函数返回后会跳转到该地址。这也就是我们常说的函数中断前的“保护现场”。这一过程是编译器隐含完成的,实际上是将EIP(指令指针)压栈,即隐含执行了一条push eip指令,在中断函数返回时再从栈中弹出该值到EIP,程序继续往下执行。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & &图 &6
& & &继续往下看,进入g_func函数后的第一条指令是push ebp,即将ebp入栈。因为每一个函数都有自己的栈区域,所以栈基址也是不一样的。现在进入了一个中断函数,函数执行过程中也需要ebp寄存器,而在进入函数之前的main函数的ebp值怎么办呢?为了不被覆盖,将它压入栈中保存。
下一条mov ebp, esp 将此时的栈顶地址作为该函数的栈基址,确定g_func函数的栈区域(ebp为栈底,esp为栈顶)。
  再往下的指令是sub esp, 48h,指令的字面意思是将栈顶指针往上移动48h Byte。那为什么要移动呢?这中间的内存区域用来做什么呢?这个区域为间隔空间,将两个函数的栈区域隔开一段距离,如图7所示。而该间隔区域的大小固定为40h,即64Byte,然后还要预留出存储局部变量的内存区域。g_func函数有两个局部变量x和y,所以esp需移动的长度为40h+8=48h。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &&
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & 图 &7
 接下来的几行指令(如下)是将刚才留出的48h的内存区域赋值为0CCCCCCCCh。
&& lea & & & & & & &edi,[ebp-48h]
0040103C&& mov & & & & &&ecx,12h
&& mov&&&&& &&&&& eax,0CCCCCCCCh
&& rep stos & & dword ptr [edi] 。
  接下来三条压栈指令,分别将EBX,ESI,EDI压入栈中,这也是属于“保护现场”的一部分,这些是属于main函数执行的一些数据。EBX,ESI,EDI分别为基址寄存器,源变址寄存器,目的变址寄存器。
4、执行子函数
& & &继续往下看,接下来是局部变量的x和y的赋值,看汇编指令中是怎样去计算x和y的内存地址的呢?如图8所示,是基于ebp去计算的,分别是[ebp-4]和[ebp-8]。我们查看内存表可以看到相应的内存区域已经存入了0xx。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & 图 & &8
此时我们对整个内存区域中存储的内容应该非常清晰了(如图9所示)。
& & & & & & & & & & & & & & & & & & & & & & & &图 & & 9
5、恢复现场
  这时子函数部分的代码已经执行完,继续往下看,编译器将会做一些事后处理的工作(如图10所示)。首先是三条出栈指令,分别从栈顶读取EDI,ESI和EBX的值。从图9的内存数据分布我们可以得知此时栈顶的数据确实是EDI,ESI和EBX,这样就恢复了调用前的EDI,ESI和EBX值,这是“恢复现场”的一部分。
& & & & & & & & & & & & & & & & & & & & &&
& & & & & & & & & & & & & & & & & & & & & & & & & & &图 & & &&10
 第四条指令是mov esp, ebp 即将ebp的值赋给esp。那这是什么意思呢?看看图9的内存数据分布,我们就能很明白了,这条语句是让ESP指向EBP所指的内存单元,也就是让ESP跳过了一段区域,很明显跳过的恰好是间隔区域和局部数据区域,因为函数已经退出了,这两个区域都已经没有用处了。实际上这条语句是进入函数时创建间隔区域的语句 sub esp, 48h的相反操作。
  再往下是pop ebp,我们从图9的内存数据分布可以看出此时栈顶确实是存储的前EBP值,这样就恢复了调用前的EBP值,这也是“恢复现场”的一部分。
& & & &再往下是一条ret指令,即返回指令,他会怎么处理呢?注意在执行ret指令前的ESP值和EIP值(如图11所示),ESP指向栈顶的0x0040CCE3,EIP的值是0x0040CCAC(即ret指令的地址)。
& & & & & &
& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & 图 &&11
执行ret指令后我们来查看ESP和EIP值(如图12所示),此时ESP为0012FEF0,即往下移动了4Byte。显然此处编译器隐含的执行了一条pop指令。再来看一下EIP的值,变为了0x0040CCE3,这个值怎么这么熟悉呢!它实际上就是栈顶的4Byte数据,所以这里隐含执行的指令应该是pop eip。而这个值就是前面讲到过的,在调用call指令前压栈的call的下一条指令的地址。从图13中可以看出,正是因为EIP的值变成了0x0040CCE3,所以程序跳转到了call指令后面的一条指令,又回到了中断前的地方,这就是所谓的恢复断点。
& & & & & & & & & & & & & & & & & & & & & & & & & & & & 图 &&12
还没有完全结束,此时还有最后一条指令add esp, 0Ch。这个就很简单了,从图12中可以看出现在栈顶的数据是1,2,3,也就是函数调用前压入的三个实参。这是函数已经执行完了,显然这三个参数没有用处了。所以add esp, 0Ch就是让栈顶指针往下移动12Byte的位置。为什么是12Byte呢,很简单,因为入栈的是3个int数据。这样由于函数调用在栈中添加的所有数据都已清除,栈顶指针(ESP)真正回到了函数调用前的位置,所有寄存器的值也恢复到了函数调用之前。
参考博客:
& & & & &&
& & & & & & &&&
阅读(339) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。C/C++函数调用过程分析 - as_ - 博客园
随笔 - 150, 文章 - 0, 评论 - 140, 引用 - 0
这里以一个简单的C语言代码为例,来分析函数调用过程
1 #include &stdio.h&
3 int func(int param1 ,int param2,int param3)
int var1 = param1;
int var2 = param2;
int var3 = param3;
printf("var1=%d,var2=%d,var3=%d",var1,var2,var3);
return var1;
13 int main(int argc, char* argv[])
int result = func(1,2,3);
首先说明,在堆栈中变量分布是从高地址到低地址分布,EBP是指向栈底的指针,在过程调用中不变,又称为帧指针。ESP指向栈顶,程序执行时移动,ESP减小分配空间,ESP增大释放空间,ESP又称为栈指针。
下面来逐步分析函数的调用过程
1.函数main执行,main各个参数从右向左逐步压入栈中,最后压入返回地址
2.执行第15行,3个参数以从左向右的顺序压入堆栈,及从param3到param1,栈内分布如下图:
&3.然后是返回地址入栈:此时的栈内分布如下:
4.第3行函数调用时,通过跳转指令进入函数后,函数地址入栈后,EBP入栈,然后把当前ESP的值给EBP,对应的汇编指令:
mov ebp esp
& &此时栈顶和栈底指向同一位置,栈内分布如下:
5.第5行开始执行,&int var1 = param1; int var2 = param2; int var3 = param3;按申明顺序依次存储。对应的汇编:
mov 0x8(%ebp),%eax
mov %eax,-0x4(%ebp)
& 其中将[EBP+0x8]地址里的内容赋给EAX,即把param的值赋给EAX,然后把EAX的中的值放到[EBP-4]这个地址里,即把EAX值赋给var1,完成C代码&int var1 = param1,其他变量雷同。
6.第9行,输出结果,第10行执行 对应的汇编代码:
-0x4(%ebp),%eax
&最后通过eax寄存器保存函数的返回值;
7.调用执行函数完毕,局部变量var3,var2,var1一次出栈,EBP恢复原值,返回地址出栈,找到原执行地址,param1,param2,param3依次出栈,函数调用执行完毕。图略温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
阅读(602)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
在LOFTER的更多文章
loftPermalink:'',
id:'fks_080065',
blogTitle:'利用宏来生成C++函数的注释, 减少写代码时重复输入的烦恼',
blogAbstract:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}1554人阅读
(1)寻找一个参数完全匹配的函数,如果找到了就调用它。
(2)在1失败后,寻找一个函数模板,使其实例化,产生一个匹配的函数模板,
& & & & &若找到了,就调用它。
(3)在1、2均失败后,再试一试低一级的对函数的重载方法,例如通过类型转换
& & & & &可产生参数匹配等,若找到了,就调用它。
(4)若以上均失败,则就是一个错误的调用。
#include&iostream&
template &class T&
T max(T x, T y)
cout&&&模板函数:max=&;
return (x&y)?x:y;
int max(int x, int y, int z=0)
cout&&&********int********&;
cout&&&int 重载函数:max=&;
return (x&y)?x:y;
m=(x&y)?x:y;
return (m&z)?m:z;
char max(char x, char y)
cout&&&char 重载函数:max=&;
return (x&y)?x:y;
int main()
int i1=2, i2=3, i3=5;
char c1='a', c2='c', c3='d';
double f1=2.45, f2=5.68;
cout&&max(i1, i2)&&
cout&&max(i1, i2, i3)&&
cout&&max(c1, c2)&&
cout&&(char)max(c1,c2,c3)&&
cout&&max(f1,f2)&&
&&相关文章推荐
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:550304次
积分:5338
积分:5338
排名:第4585名
原创:159篇
转载:23篇
评论:51条
(1)(40)(24)(2)(1)(2)(13)(10)(6)(8)(19)(2)(7)(4)(2)(1)(11)(3)(2)(1)(23)}

我要回帖

更多关于 函数注释 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信