关于栈帧最重要的事——

关于栈帧,我们需要了解一下C/C++程序内存的分配,如图所示:
这里写图片描述

栈区:由编译器自动分配释放,存放为运行函数而分配的局部变量、函数、参数、返回值数据、返回地址等。栈区地址依次逐减,与堆区相对而生。
栈帧:函数的调用过程中为函数开辟栈空间,用于本次函数的调用中临时变量的保存、现场保护。这块栈空间就是函数栈帧。
一个数据结构——栈,栈顶与栈底。
两个操作——push(入栈)与pop(出栈)。
寄存器——EAX、EBX、ECX、EDX (通用寄存器)
EBP 栈底寄存器(基址寄存器)
ESP 栈顶寄存器
EIP 指令寄存器(程序寄存器)
部分汇编——call(每人将当前正在执行指令的下一条指令的地址压入栈 中,并且跳转至目标函数的地址,开始过程调用。)
ret (弹出栈顶地址,将数据放入EIP)
VC++6.0对栈帧进行研究(不同平台下操作,原理相同偏移量不同)

   #include<stdio.h>
#include<windows.h>

int Add(int x,int y){
int z=x+y;
return z;
}

int main(){
int a=10;
int b=20;
int ret=Add(a,b);
printf("%d\n",ret);
system("pause");
return 0;
} 

将以上代码进行调试,转成汇编语言,打开寄存器与内存。
mian函数及临时变量部分
这里写图片描述
此时寄存器与内存如下这里写图片描述

栈区的情况如下
这里写图片描述

10  int a=10;
00401078   mov         dword ptr [ebp-4],0Ah //a_EAX
11:   int b=20;
0040107F   mov         dword ptr [ebp-8],14h//b_ECX
12:   int ret=Add(a,b);
00401086   mov         eax,dword ptr [ebp-8]
00401089   push        eax          //ESP下移,b_EAX
0040108A   mov         ecx,dword ptr [ebp-4]
0040108D   push        ecx          //ESP下移,a_ECX
0040108E   call        @ILT+0(Add) (00401005)
                                    //ESP下移,存入00401093

Add函数栈帧部分

4:    int Add(int x,int y){
00401020   push        ebp //ESP下移,EBP压栈
00401021   mov         ebp,esp//ESP地址_EBP
00401023   sub         esp,44h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-44h]
0040102C   mov         ecx,11h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
5:    int z=x+y;
00401038   mov         eax,dword ptr [ebp+8]//a_EAX
0040103B   add         eax,dword ptr [ebp+0Ch]//a+b_EAX
0040103E   mov         dword ptr [ebp-4],eax
6:    return z;
00401041   mov         eax,dword ptr [ebp-4]//  返回EAX

寄存器与内存
这里写图片描述

返回

00401044   pop         edi
00401045   pop         esi
00401046   pop         ebx
00401047   mov         esp,ebp
00401049   pop         ebp
0040104A   ret          //弹出栈顶地址数据,并放入EIP


00401093   add         esp,8//ESP 上移8
00401096   mov         dword ptr [ebp-0Ch],eax
13:   printf("%d\n",ret);
00401099   mov         edx,dword ptr [ebp-0Ch]
0040109C   push        edx 
0040109D   push        offset string "%d\n" (00424024)
004010A2   call        printf (00401200)

这里写图片描述

整个栈帧的调用过程完成。

小扩展:
1.证明形参实例化时由右向左的

int Add(int x,int y){
int*p=&x;
p++;   
*p=20;
int z=x+y;
return z;
}// 21
int Add(int x,int y){
int*p=&x;
p++;
//*p=20;
int z=x+y;
return z;
}// 3

2.函数内部调用函数,返回方式的研究

#include<stdio.h>
#include<windows.h>
void bug(){
printf("hello,i am a bug.\n");
system("pause");
}

int Add(int x,int y){
int*p=&x;
p--;
p=(int)bug;//导致系统奔溃
int z=x+y;
return z;
}

int main(){
int a=1;
int b=2;
int ret=Add(a,b);
printf("%d\n",ret);
system("pause");
return 0;
}

在Add函数中调用bug函数,返回值返回到bug函数中,但是编译器会崩掉,所以调用完bug函数后最终还是要返回到main函数中

这里写图片描述
bug要返回,首先得找自己的返回值
那就先来研究一下函数内部的第一个变量和第一个参数的关系:

void fun(int x)
{
    int y;
    printf("&x: %p\n");
    printf("&y: %p\n");
}

得出x和y 的地址差12
对应于上图中局部变量到00401093的地址差8,所以可以将bug函数的代码改为:

void bug()
{
    int x=0;
    int *p=&x;
    p+=2;
}

但是运行的时候编译器依然会崩掉,原因:
调用bug函数时,是指针跳转过去,没用call指令,也就是没有进行push操作
但是返回的时候执行了ret指令,也就是多pop了一次,即esp变大了一个地址,
所以还需将esp再减去4;
在c语言中插入汇编语言:–asm

   __asm
    {
        sub esp,4;

    }

这样就成功的从bug函数中返回到了main函数中.

在 Java 程序运行过程中,每当调用一个方法时,JVM 会在当前线程的(Stack)中创建一个新的记录项来保存该方法的状态信息,这就是所谓的**(Stack Frame)**。是实现函数调用的关键结构之一,在程序执行期间承担着重要的职责。下面我们将详细解释的作用及其组成部分。 ### 的功能 每次方法被调用的时候都会生成对的一个新的,并按照先进后出的原则组织成 LIFO 结构——即最后进入的方法最先返回。每个包含了执行该段代码所需的所有局部环境数据和控制信息,主要包括以下几个方面: 1. **局部变量表(Local Variables Table)** - 存储了方法内部声明的各种类型的局部变量、形式参数以及一些临时操作数等基本信息。 - 局部变量的位置由字节码指令指明,通常以索引来标识具体的槽位。 2. **操作数(Operand Stack)** - 可视作一个小的工作空间,用于暂存中间计算结果或者传递给下一组命令的操作数值。 - 当遇到诸如算术运算符这样的二元操作时,会先从顶弹出两个元素参与运算然后再把得到的结果压回去备用。 3. **动态链接(Dynamic Linking Information)** - 包含返回地址 Return Address (指向调用者所在位置),以便于 JVM 能够准确无误地找到当跳转回哪里继续往下走; - 同时也维护了一个对上层父级引用 Parent Frame Reference 的追踪链路方便后续访问外部作用域内的成员属性或其它上下文内容。 4. **常量池引用(Constant Pool References)** - 如果涉及到静态字段加载或是字符串拼接之类的动作那么就需要借助于此处存放的相关符号引用进行解析映射至实际物理内存地址上去获取对的值。 5. **异常处理器表(Exception Handler Table)** - 描述了本层次内发生的错误情况该如何捕获并处理的信息列表,规定了一旦抛出了特定种类未被捕获异常则立即转向指定标签处开始一段特殊流程。 6. **正常退出状态与非正常退出状态** - 正常情况下,当方法体被执行完毕之后便可以直接销毁对恢复之前的状态; - 若中途遇到了意外状况如 throw 出 Exception 对象,则依据预先设定好的规则寻找匹配项实施相措施直至恢复正常运作为止。 综上所述,可以说每一个活跃中的方法都有且仅有一个与其关联独一无二的相伴相随,它们共同构成了我们熟知的过程抽象机制的核心部分,保证了各层级之间清晰独立又彼此协调工作的可能性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值