前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >call stack详解

call stack详解

作者头像
全栈程序员站长
发布2022-11-17 17:53:06
8000
发布2022-11-17 17:53:06
举报

call stack详解:

调用堆栈:调用堆栈是一个方法列表,按调用顺序保存所有在运行期被调用的方法。

栈:在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶 指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

当发生函数调用的时候,栈空间中存放的数据是这样的:   1、调用者函数把被调函数所需要的参数按照与被调函数的形参顺序相反的顺序压入栈中,即:从右向左依次把被调函数所需要的参数压入栈;   2、调用者函数使用call指令调用被调函数,并把call指令的下一条指令的地址当成返回地址压入栈中(这个压栈操作隐含在call指令中);   3、在被调函数中,被调函数会先保存调用者函数的栈底地址(push ebp),然后再保存调用者函数的栈顶地址,即:当前被调函数的栈底地址(mov ebp,esp);   4、在被调函数中,从ebp的位置处开始存放被调函数中的局部变量和临时变量,并且这些变量的地址按照定义时的顺序依次减小,即:这些变量的地址是按照栈的延伸方向排列的,先定义的变量先入栈,后定义的变量后入栈;   所以,发生函数调用时,入栈的顺序为:   参数N   参数N-1   参数N-2   …..   参数3   参数2   参数1   函数返回地址   上一层调用函数的EBP/BP   局部变量1   局部变量2   ….   局部变量N

解释:   首 先,将调用者函数的 EBP入栈(push ebp),然后将调用者函数的栈顶指针ESP赋值给被调函数的EBP(作为被调函数的栈底,mov ebp,esp),此时,EBP寄存器处于一个非常重要的位置,该寄存器中存放着一个地址(原EBP入栈后的栈顶),以该地址为基准,向上(栈底方向)能 获取返回地址、参数值,向下(栈顶方向)能获取函数的局部变量值,而该地址处又存放着上一层函数调用时的EBP值;   一般而言,SS: [ebp+4]处为被调函数的返回地址,SS:[EBP+8]处为传递给被调函数的第一个参数(最后一个入栈的参数,此处假设其占用4字节内存)的 值,SS:[EBP-4]处为被调函数中的第一个局部变量,SS:[EBP]处为上一层EBP值;由于EBP中的地址处总是”上一层函数调用时的EBP 值”,而在每一层函数调用中,都能通过当时的EBP值”向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取被调函数的局部变量值”;   如此递归,就形成了函数调用栈;   函数内局部变量布局示例:   #include <stdio.h>   #include <string.h>   struct C   {   int a;   int b;   int c;   };   int test2(int x, int y, int z)   {   printf(“hello,test2\n”);   return 0;   }   int test(int x, int y, int z)   {   int a = 1;   int b = 2;   int c = 3;   struct C st;   printf(“addr x = %u\n”,(unsigned int)(&x));   printf(“addr y = %u\n”,(unsigned int)(&y));   printf(“addr z = %u\n”,(unsigned int)(&z));   printf(“addr a = %u\n”,(unsigned int)(&a));   printf(“addr b = %u\n”,(unsigned int)(&b));   printf(“addr c = %u\n”,(unsigned int)(&c));   printf(“addr st = %u\n”,(unsigned int)(&st));   printf(“addr st.a = %u\n”,(unsigned int)(&st.a));   printf(“addr st.b = %u\n”,(unsigned int)(&st.b));   printf(“addr st.c = %u\n”,(unsigned int)(&st.c));   return 0;   }

int main(int argc, char** argv)   {   int x = 1;   int y = 2;   int z = 3;   test(x,y,z);   printf(“x = %d; y = %d; z = %d;\n”, x,y,z);   memset(&y, 0, 8);   printf(“x = %d; y = %d; z = %d;\n”, x,y,z);   return 0;   }   打印输出如下:   addr x = 4288282272   addr y = 4288282276   addr z = 4288282280   addr a = 4288282260   addr b = 4288282256   addr c = 4288282252   addr st = 4288282240   addr st.a = 4288282240   addr st.b = 4288282244   addr st.c = 4288282248   a = 1; b = 2; c = 3;   a = 0; b = 0; c = 3; 示例效果图:

call stack详解
call stack详解

该图中的局部变量都是在该示例中定义的;

call stack详解
call stack详解

这个图片中反映的是一个典型的函数调用栈的内存布局;

访问函数的局部变量和访问函数参数的 区别: 局部变量总是通过 将ebp减去偏移量来访问,函数参数总是通过 将ebp加上偏移量来访问。对于32位变量而言,第一个局部变量位于ebp-4,第二个位于ebp-8,以此类推,32位局部变量在栈中形成一个逆序数组;第一个函数参数位于ebp+8,第二个位于ebp+12,以此类推,32位函数参数在栈中形成一个正序数组。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/223055.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年10月29日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档