1.栈区的特点

栈区是从高地址向低地址方向生长:高地址是栈底,低地址是栈顶,也使用高地址空间在使用低地址空间。

但由起始地址(地址最低的地址),存放变量的字节地址是顺序且递增的

这就是为什么数组的元素是地址是递增的,虽然栈是从高地址向低地址方向生长,但是数组是整体在栈上开辟空间,数组的其他元素的地址是依次递增
2.函数栈帧
在讲函数栈帧之前先看常用的汇编指令,和寄存器

接下来就以main函数调用Add函数为例,详细阐述调用Add函数,形成函数栈帧,函数调用完毕释放栈帧的详细过程,其中涉及到形成临时变量,形成函数栈帧,函数结束如何销毁栈帧,如何返回到调用Add函数的main中继续执行后面的代码。

vs2013 有栈随机化的处理(有关数据的地址可能不一样),重新运行代码可能会导致,每次看到的相关数据可能会不太一致,不过我们重点关注变化原理和过程,
先看一下函数调用的整个过程

接下来就是逐段代码详细讲解
main函数也是被其他函数调用

main函数被其他函数调用,则形成main函数的栈帧(在栈分配一块内存)。


这里如何形成的main栈帧,等讲完main函数调用Add函数形成Add函数的栈帧过程就理解了。

int x =0xA对应的汇编代码执行前

int x =0xA对应的汇编代码执行后

int y =0xB 与 int z =0 执行完之后,与上面过程类似

三个变量分配内存并初始化。

形成x,y的临时变量a,b(形参)








总结:

1.临时变量的形成是在函数正式被调用就形成了的
2.形参实例化的顺序是从右向左

接下来开始调用Add函数


函数调用包含两个
1.压入返回地址(入栈返回地址)
2.转入目标函数
第二好理解,要调用Add函数肯定要转入Add函数中执行Add函数的代码,但等函数调用完毕得返回main函数中在继续执行后续代码,所以必须保存返回地址->返回main函数调用Add函数的下一条指令。



返回地址入栈,栈顶上移

接下来就是创建Add函数栈帧,但在这之前要线存储main函数栈底地址保存下来(入栈),因为Add函数调用完毕之后,销毁栈帧,此时得栈底指针ebp与栈顶指针esp要重新指向main函数栈帧栈底与栈顶,所以必须提前保存main函数栈帧栈底地址。

先保存main函数栈帧栈底地址




接下来就是形成Add函数的栈帧
第一步:


第二步


总结:

当函数被调用时,即Add函数被调用时,编译器就会自动形成Add函数栈帧,至于函数的栈帧大小,编译器也会自己根据函数中变量与变量的类型来估计函数栈帧的大小,总之一句话函数栈帧由编译器搞定
Add函数栈帧开辟成功后,则开始执行Add函数中的代码,实现变量分配内存并初始化及数据运算。

int c =0 对应的汇编代码执行前

int c =0 对应的汇编代码执行后

此时将c变量分配空间并初始化为0

接下来就是进行加法运算


ebp+8就是保存的0xA也就是a变量
ebp+c就是保存的0xB也就是b变量


此时就完成了c=a+b

最后返回的时候将c的值写入eax临时寄存器中,也就是说Add函数的返回值是通过CPU中的临时寄存器返回

接下来就是函数调用完毕,释放Add函数的栈帧,返回main函数中执行,栈底指针ebp与栈顶指针esp重新指向main函数的栈帧栈底与栈顶


下图这句代码相当于释放Add函数的栈帧


使栈底指针ebp重新指向main函数栈帧栈底



此时eip寄存器拿到栈顶的返回地址,则即可返回main函数中执行main函数后续代码

回到main函数中,直接执行
add esp,8 即让esp后移8个单位,即释放原来的临时变量

自此Add函数栈帧销毁,所以入栈元素全部销毁包括原来入栈的两个临时变量

接下来就是接收返回值,前面已经讲了返回值c变量的值已经存储在eax临时寄存器,现在将eax中的返回值0x15,移动到ebp-20h中而ebp-20h内容就是z变量,相当于将返回值放入z变量中


至此整个调用Add函数创建栈帧,执行完毕后释放栈帧过程全部完毕
其他函数的调用也就类似,main函数也是被其他函数调用,当main函数被调用时,编译器自动形成main函数栈帧,等main函数执行完毕后也会释放main的栈帧
总结:
函数调用返回的整个过程

1.调用函数,需要先形成临时拷贝,形成过程是从右向左的
2.临时空间的开辟,是在对应函数栈帧内部开辟的,函数调用完毕,栈帧结构被释放掉,因此函数中的变量的空间也随之释放,所以临时变量具有临时性。
3.调用函数是有成本的,成本体现在时间和空间上,本质是形成和释放栈帧有成本
4. 函数调用,因拷贝所形成的临时变量,变量和变量之间的位置关系是有规律的
