目录
前言:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
——By 作者:新晓·故知
那些代码背后的故事:
通过反汇编讲解函数栈帧的创建与销毁!
注:本次编译环境为:Visual Stdio 2013 !
越高级的编译器越难以抽离函数栈帧分装的过程!
main函数也会被其他函数调用哦!
反汇编查看:
为a、b、c开辟空间并存值:
——By 作者:新晓·故知 整理+创作
完成a的开辟、存值:
完成b的开辟、存值:
完成c的开辟、存值:
push 压栈:
传参:
call指令将下一条指令的地址拿出压栈!
为Add函数准备栈帧!
——By 作者:新晓·故知 整理+创作
形参对应的函数(子函数)计算:
注:
函数调用时没有创建形参,最初在调用函数时,通过call指令和其他指令,就将参数传过去了,将实参a、b通过push压栈在a'、b'。因为是在栈中进行,所以先传的是b(右面的参数),压入栈中,再传a(左面的参数),故参数从右向左传参!
当进入函数计算时,形参回头去找压栈时的空间的a、b对应的值!
因此说:形参是对实参的临时拷贝!改变形参的值不影响实参!
返回:
先在eax保存z的值,安全!
此时所有指令回到main函数!main函数的栈帧交给esp,ebp进行维护!Add函数的栈帧销毁!别担心!z的值在寄存器eax中存放,安全着呢!
这时显现出最初在栈顶存储call指令的下一条指令地址的作用:
在进行函数调用结束后回到主函数,并从call指令的下一条指令开始执行!
逻辑严谨!不仅要走的出去,也要回的来!
esp执行至此,形参的空间均已释放归还给操作系统!
而eax的值暂存的z=30!主函数进行打印输出!
解答:
1.局部变量的创建是首先为函数分配栈帧空间,栈帧空间初始化一部分空间后,再为局部变量分配一些空间!
2.局部变量不初始化的是随机值,因为创建的时候是随机放置的值!初始化后随机值被覆盖!
3.函数的传参是在未调用形参对应的函数(子函数)时通过push 操作将实参的值压栈,当真正进入形参函数时,在形参对应的函数(子函数)栈帧里通过指针的偏移量,找回形参,进行使用!
传参是从右向左传!
4.形参是在压栈时开辟的空间 ,它和实参只是在数值上相同的,空间上是独立的!
形参是实参的一份临时拷贝!改变形参的值不影响实参!
5.函数的调用见以上讲解!
6.函数调用的结果的返回:
调用之前将call指令的下一条指令的地址压入栈,记录存储,将ebp的调用的函数的上一个函数(此例为主函数ebp-main)的栈帧的ebp地址存储记录,当形参对应的函数(子函数)调用完返回时,弹出ebp就找到原始函数(上一个函数,此例为main函数)的ebp,而esp的指针移动返回时就能找到原始函数(此例为main函数)的栈帧的顶,回到原始函数(main函数)的栈帧空间,就可以跳转到已记录的call指令的下一条指令的地址,进行返回。
返回值是通过寄存器eax带回!
注:
函数内部创建的静态变量是在全局变量空间开辟的!而以上介绍的是在栈区空间开辟的!
编译器会根据函数的不同,开辟合适的空间!
1.a和b是不连续的,相隔的空间大小取决于不同的编译器!
2.函数的形参可以理解为:不在形参对应的函数(子函数)的栈帧里,而在主函数main拓展的栈帧里!
——Since 作者:新晓·故知 整理+创作
前言:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
——By 作者:新晓·故知
那些代码背后的故事:
通过反汇编讲解函数栈帧的创建与销毁!
注:本次编译环境为:Visual Stdio 2013 !
VS2013运行结果会一闪而过 解决办法1: 设置项目属性 解决办法2: system("pause");
越高级的编译器越难以抽离函数栈帧分装的过程!
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); return 0; }
main函数也会被其他函数调用哦!
反汇编查看:
为a、b、c开辟空间并存值:
——By 作者:新晓·故知 整理+创作
完成a的开辟、存值:
完成b的开辟、存值:
完成c的开辟、存值:
push 压栈:
传参:
call指令将下一条指令的地址拿出压栈!
为Add函数准备栈帧!
——By 作者:新晓·故知 整理+创作
33h次:16进制的33h次
形参对应的函数(子函数)计算:
注:
函数调用时没有创建形参,最初在调用函数时,通过call指令和其他指令,就将参数传过去了,将实参a、b通过push压栈在a'、b'。因为是在栈中进行,所以先传的是b(右面的参数),压入栈中,再传a(左面的参数),故参数从右向左传参!
当进入函数计算时,形参回头去找压栈时的空间的a、b对应的值!
因此说:形参是对实参的临时拷贝!改变形参的值不影响实参!
返回:
先在eax保存z的值,安全!
此时所有指令回到main函数!main函数的栈帧交给esp,ebp进行维护!Add函数的栈帧销毁!别担心!z的值在寄存器eax中存放,安全着呢!
这时显现出最初在栈顶存储call指令的下一条指令地址的作用:
在进行函数调用结束后回到主函数,并从call指令的下一条指令开始执行!
逻辑严谨!不仅要走的出去,也要回的来!
esp执行至此,形参的空间均已释放归还给操作系统!
而eax的值暂存的z=30!主函数进行打印输出!
解答:
1.局部变量的创建是首先为函数分配栈帧空间,栈帧空间初始化一部分空间后,再为局部变量分配一些空间!
2.局部变量不初始化的是随机值,因为创建的时候是随机放置的值!初始化后随机值被覆盖!
3.函数的传参是在未调用形参对应的函数(子函数)时通过push 操作将实参的值压栈,当真正进入形参函数时,在形参对应的函数(子函数)栈帧里通过指针的偏移量,找回形参,进行使用!
传参是从右向左传!
4.形参是在压栈时开辟的空间 ,它和实参只是在数值上相同的,空间上是独立的!
形参是实参的一份临时拷贝!改变形参的值不影响实参!
5.函数的调用见以上讲解!
6.函数调用的结果的返回:
调用之前将call指令的下一条指令的地址压入栈,记录存储,将ebp的调用的函数的上一个函数(此例为主函数ebp-main)的栈帧的ebp地址存储记录,当形参对应的函数(子函数)调用完返回时,弹出ebp就找到原始函数(上一个函数,此例为main函数)的ebp,而esp的指针移动返回时就能找到原始函数(此例为main函数)的栈帧的顶,回到原始函数(main函数)的栈帧空间,就可以跳转到已记录的call指令的下一条指令的地址,进行返回。
返回值是通过寄存器eax带回!
注:
函数内部创建的静态变量是在全局变量空间开辟的!而以上介绍的是在栈区空间开辟的!
编译器会根据函数的不同,开辟合适的空间!
1.a和b是不连续的,相隔的空间大小取决于不同的编译器!
2.函数的形参可以理解为:不在形参对应的函数(子函数)的栈帧里,而在主函数main拓展的栈帧里!