从指令角度深入理解函数调用堆栈详细过程

从指令角度深入理解函数调用堆栈详细过程

在学习任何一门高级编程语言时,我们都离不开函数的调用,但是无论是库函数,还是我们自己声明的函数,我们都不仅仅要了解如何去实现该函数,更加需要了解的是其底层的调用原理。本文将从汇编指令的角度去深入理解函数调用堆栈的详细过程。有了底层原理的理解,对于我们学习有着深远的意义。
我们来看这样一个例子:

int sum(int a, int b)
{
 int temp = 0;
 temp = a + b;
 return temp;
}
int main()
{
 int a = 10;
 int b = 20;
 int ret = sum(a, b);
 printf("ret=%d\n", ret);
 return 0;
}

这是一段简单的C语言代码,在这段代码中,我们定义了一个sum(求两个整形变量的和)函数,返回值为整形,还定义了一个main函数。在此,我们需要解决两个问题:
1.main函数调用sum,sum执行完之后如何知道返回到main函数中?
2.sum函数执行完,回到main函数后如何知道从哪一行开始执行?

大家也许都清楚,函数运行时要在栈帧上开辟空间,在函数代码指令上,访问局部变量,都是通过栈底指针的偏移来访问的。程序编译后,定义的局部变量a、b、ret会生成相对应的汇编指令:
int a=10-->mov dword ptr[ebp-4],0Ah//将a的值存储到ebp-4的位置
int b=20-->mov dword ptr[ebp-8],14h//将b的值存储到ebp-8的位置
mov dword ptr[ebp-0C],26h//将ret存储到ebp-0C的位置处
当运行这两条指令时CPU会在栈上分别为局部变量a、b、ret开辟空间4字节的空间并将局部变量a、b的值进行移值,由于ret的值只有在函数调用后才会生成,所以暂时没有值。如下图所示:
在这里插入图片描述
接下来就是调用sum函数的过程了。
强调一点:在栈中每压栈一次栈顶地址将会更新一次。
在调用之前首先做的一件事就是向栈里压参数,参数从右向左进行压栈,调用方为其开辟内存。所以,先将参数b进行压栈,产生的汇编指令为:

    mov eax,dword ptr[ebp-8]//先从ebp-8的位置取值,存储在寄存器中
    push eax//压栈

进而是对参数a进行压栈,其汇编指令为:

   mov eax,dword ptr[ebp-4]//先从ebp-4的位置取值,存储在寄存器中
   push eax//压栈

进而开始调用函数:

call sum//调用函数sum
//call指令会做两件事情:
//1.将下一条指令add esp,8的地址进行压栈
//2.进入函数
add esp,8//当sum函数调用完毕时,将栈中空间交还给系统
mov dword ptr[ebp-0Ch],eax//将sum函数的返回值存储到ebp-0Ch(ret)处

调用函数后首先遇到的 “{” 产生指令:

push ebp//将main函数的栈底地址入栈
//此处进入sum函数的栈帧 为sum函数开辟栈帧空间
mov ebp,esp//将esp的值赋给ebp
sub esp,4Ch//将栈顶地址更新

接下来执行函数体:产生的汇编指令为:


int tmp-->mov dword ptr[ebp-4],0 //将temp的值0存放在栈中
temp=a+b-->mov eax,dword ptr[ebp+0Ch]//将ebp+0Ch处的值存储到eax中,即b的值
           add eax,dword ptr[ebp+8]//将ebp+8处的值,即a的值取出并与eax(a的值)中的值相加并存储到eax中
return temp-->mov eax,dword ptr[ebp-4]//将temp的值通过寄存器将值返回

sum函数结束时 “}” 产生的指令为:

mov esp,ebp//栈帧回退,对栈中的数据并不进行清理
pop ebp//出栈,并将出栈元素赋给ebp,ebp回退至main函数栈帧的栈底处
ret//做两件事:1.出栈。2.把出栈的内容放入CPU的PC寄存器里面

此时,函数调用就结束了,接下来就是将函数值返回之main函数中并且将空间交还给系统,我们已经在其指令产生处进行描述了。
函数详细调用堆栈如图所示:

此图中ebp以及esp均为调用结束后所处位置。
现在,我们可以回答最开始提出的两个问题了:
因为从main函数进入sum函数时,就已经通过push指令把调用方(main)栈底的地址压到当前栈顶处了,当sum函数调用完时通过pop指令将这个地址又赋给ebp,即又回退到mian函数栈帧的栈底处;执行完ret指令后CPU将在PC寄存器中取下一条指令的地址(ret指令做的第二件事就是将call指令压入栈中的下一条指令的地址取出存储到PC寄存器中),即add esp,8指令的地址,从而实现栈帧回退,接下来执行mov dword ptr[ebp-0Ch],eax指令,将sum函数的返回值存储到ebp-0Ch(ret)处。
由此 函数调用堆栈的全过程结束。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值