关于栈帧 我觉得这两篇文章讲的不错了,至少我看完能 明白了。
http://www.cnblogs.com/haolujun/archive/2011/03/26/1996525.html
http://blog.csdn.net/zsy2020314/article/details/9429707
写的很详细,我不打算和他们一样的从头分析这个栈帧了 。
通过理解栈帧这个概念我觉得能够理解 函数中的变量为什么会有局部性,一层层的函数调用是怎么实现的 又 是怎么正确返回的等等。 然后在思考函数调用时栈的结构时发现一个问题。
先简单写两个函数
#include<stdio.h>
#include<unistd.h>
int foo()
{
printf("where i am\n");
exit(0);
}
int fun(int a,int b)
{
int c=5;
int *p = &a;
*(p-1)= foo;
return c;
}
int main()
{
int a = 0;
int b = 1;
int ret = fun(a,b);
return 0;
}
画图水平不行 从上边文章中盗个图改改
在main中调用fun时 在汇编角度也就是call fun时 call指令 会将 下一句指令的地址入栈,以备函数返回时继续执行,从fun返回是 ret 相当于 pop IP IP就指向了先前有call入栈的fun的下一句的地址。
也就是说从fun中能正确返回到main中 靠的是 图中在 变量a下一个位置存储的“返回地址” 而 a 是被调用函数的第一个参数,我们在fun中完全能找到它,那么我既然能找到a 也就能找到“返回地址”所存储的位置,当然也就能修改它了
int *p = &a;
*(p-1)= foo;
这两个语句 将“返回地址”修改成了foo函数,所以当程序执行时 fun结束,就不能争取返回到main中了,而是跑到了foo中~~~~
结果会输出 where i am
如果foo中 不exit的话 那程序也无法正常返回到main中结束了,因为之前的修改已经打乱了 层层调用的这种栈帧结构,程序会出错。由此例考虑,利用这个栈帧 应该还能做很多有意思的事情
学习了栈帧这个货之后,我突然想到了前段时间调试程序时一个解释不了的bug,原程序解释起来比较啰嗦,我就举个同理的例子好了。简单说就是在用gdb调程序时,比如我刚走到第五行,然后打印第十行的变量,发现它的值已经被计算出来了,这gdb你也太不老实了,还没到那一行呢 值怎么就出来了呢?当时令我有些困惑,但看完栈帧后,似乎知道为什么了。 先写个程序测试一下。
简单看 在main中两次调用fun 用gdb调试,当进入到第二个fun时,跑到第五行
int c = 3;
这是后 打印 后边的变量 d f 竟然已经有了 正确的值, 我明明还没跑到那句呢? 然后发现调试进入第一个fun调用的话,就不会发生这种现象。
然后 想----------- 应该是两次都调用了同一个函数,当第一个函数返回后 紧接着调用第二个,因为是同一个函数,那么 栈帧,及内部临时变量分配的存储地址,都是一样的
,所以当我在第二个fun中打印后边变量的内容时,并不是已经执行到了那一句,而是 d f 内容应是内存中的随机值,而这个内存中的随机值就正好是上一次调用fun时 临时变量的值。
再来验证一下上边的想法, 把函数改一下
这次就能很容易的看出 在第二次调用 fun时 跑第五行,打印后边的变量,这些变量的内容是第一次调用函数时那些变量的值,d=2 f=3 而不是像最初所想的那样,它提前算出来了(d= 6 f =11)
那再改一下
可以看到 虽然是不同的函数 但函数结构 变量个数顺序什么的都一样, 栈帧结构也是相同的。