🎁个人主页:我们的五年
🔍系列专栏:c语言课程学习
🎉欢迎大家点赞👍评论📝收藏⭐文章
目录
问题:
1.局部变量是怎么创建的?
调用函数的时候,会为函数开辟一块空间,然后第一个局部变量从栈低分配一块空间给局部变量。
2.为什么局部变量的值是随机的?因为在为函数开辟空间的时候,这块空间的里的内存都被初始化为一个值,不同编译器中值可能不同。如果只是创建局部变量,没有初始化覆盖里面的值,那此时局部变量里的值就是为函数内存初始化的值。
3.函数是怎么传参的?传参的顺序是怎样的?调用函数的时候,会在该函数的栈顶压栈,该内存里放的是形参,然后通过ebp的偏移量就可以找到形参。 传参的顺序是从右到左。
4.形参和实参是什么关系?形参是实参的零时拷贝,形参是在函数栈顶新开辟的一块空间,这块空间与实参的空间相互独立,只是数值一样。改变形参不会改变实参。
5.函数调用是怎么做的?函数会把函数的下一指令的地址保存在call中,并且保存ebp的位置。
然后为新的函数开辟空间,在新的函数的栈顶压入三个寄存器ebx,esi,edi,然后新的空间全部初始化为一个值。然后就执行新新函数里的指令。
6.函数调用以后是怎么返回的?返回值是通过寄存器带回来的,寄存器是全局的,不会因为函数的销毁而销毁。
然后根据保存的ebp,和下一指令的地址找到新的函数,即要执行的指令的地址。
不同的函数会开辟不同的空间。
1.ebp,esp两个寄存器用来维护函数栈帧
1.ebp寄存器:栈底寄存器。
2.esp寄存器:栈顶寄存器。
3.pc指针寄存器:也叫程序计数器,它永远指向当前指令的下一条指令。
当前有下面代码:
cpp
#define CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d", c);
return 0;
}
●这个程序中,我们先进入main函数,esp寄存器和ebp寄存器就用来维护main函数的函数栈帧。当指令到达c=Add(a,b);的时候,就要调用Add函数,这时候,esp寄存器和ebp寄存器就要区维护Add函数的函数栈帧。
●每一个函数都有自己的ebp和esp,即每个函数都有自己的栈顶和栈底。
●在CPU中,ebp和esp不会有很多,但是函数却有很多。但是程序运行的时候,不可能一边运行两个函数,当运行main函数的时候,ebp和esp去维护main函数的函数栈帧。当进入Add函数,去运行Add函数的时候,esp和ebp就去维护Add函数的函数栈帧,并且把mian函数的esp和ebp的指针保存下来,等Add函数结束以后,esp和ebp就又可以去维护main函数的函数栈帧。
●地址的使用顺序是从高地址,到低地址。
2.main函数也一个函数,main函数也要被其他函数调用
当main函数结束以后,函数栈帧回到__tmianCRTStartup()。
●由此可知main函数被__tmainCRTStartup()调用。
●__tmainCRTStartup()函数被mainCRTStartup()函数调用。
程序先进入mainCRTStartup函数,为mainCRTStartup函数开辟函数栈帧,然后调用__tmainCRTStartup函数,为__tmainCRTStartup函数开辟函数栈帧。所以上面的图中比main高地址处还有这两个函数的函数栈帧,这两个函数的函数栈帧比main函数的函数栈帧高。
3.函数栈帧创建的过程
在调用main函数时,已经存在来到下面情形,__tmainCRTStartup()调用main函数
🚗1.push ebp:在栈顶,在栈顶把ebp指针的值放进去。esp就要改变位置,往上移一点,栈顶改变,esp也要改变。指针的大小是四个字节,在栈顶存ebp的值,所以就开辟4个字节的空间,里面放着ebp的值。
🚗 2.mov ebp,esp:把esp的值给ebp,那么ebp就指向esp指向的地方。
🚗3.sub esp,0E4H:0E4H是一个16进制数,转化为10进制是228.让esp减小228。减小是往上增长,往低地址处变化。
🚗4.push ebx
push esi
push edi:
在栈顶压入ebx,esi,edi,esp跟着变化。
🚗5.lea=lode effective address
把edi往下的到ebp内存里的值全部变为0cccccccch.word是两个字节,dword是double word占4个字节。
🚗6显示地址以后:
🚗7.Add函数调用
[ebp+8]找到a,[ebp+0ch]找到b,b给eax,然后eax+(add)a,然后eax给z。Add函数中没有创建形参a,b。
4.函数栈帧的销毁过程
【ebp-8】表示z,也就是把z的值给寄存器eax,这样z的值就不会丢失。
三次pop销毁edi,esi,ebx。
ebp的值给esp,esp就指向ebp指向的位置。
然后pop ebp,把ebp指向空间的值给ebp也就是main ebp。
然后ret call的下一条指令的地址。使程序可以从main函数调用完Add函数以后,执行Add函数的下一条指令。
然后把main函数的指令执行完。
最后把main函数的函数栈帧给销毁。
回到__tmainCRTStartup函数。