计算机中堆栈指针的作用,函数调用栈空间变化简析-变量作用域原理及传递指针的好处...

本帖最后由 xiniuniu 于 2015-4-10 15:35 编辑

#include

int fun2(int x, int y, int z)

{

int i = x + y;

int j = y + z;

int k = i + j;

return k;

}

int fun1(int a, int b)

{

int c = a + b;

int d = 0;

d = fun2(a, b, c);

return d;

}

int main()

{

int num1 = 10;

int num2 = 20;

fun1(num1, num2);

return 0;

}复制代码

以上边代码为例,main函数调用了fun1函数, 而fun1函数又调用了fun2函数。在调用过程中栈空间的变化如下

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

01.png (2.05 KB, 下载次数: 0)

2015-4-10 14:34 上传

在fun1调用前 目前是main函数的栈空间num1变量已经赋值为10, num2变量赋值为20;

ESP 是栈指针寄存器这个寄存器中存储着栈顶的地址。 EBP中存储着栈底的地址。 函数栈空间主要是由这两个寄存器来确定

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

02.png (2.52 KB, 下载次数: 0)

2015-4-10 14:37 上传

main函数调用fun1函数时,第1 步操作就是把传入的参数压入到栈中。 c语言使用的是_cdecl调用方式,参数从右向左依次压入栈中

所以实参num2的数值被复制到了形参b所在的内存中

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

03.png (2.71 KB, 下载次数: 0)

2015-4-10 14:42 上传

接着又将实参num1的数值复制到了形参a所在的内存中。ESP的数值也随着不断减小以指向栈顶

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

04.png (3.06 KB, 下载次数: 0)

2015-4-10 14:44 上传

调用函数结束后都会返回到代码所调用行的下一行继续执行。那么他是怎样知道返回后要继续执行哪块地址上的命令呢。参数压入栈空间后,接下来的工作就是保存被调用函数返回后要执行指令的地址。也就是保存到当前ESP所指向的栈空间中

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

05.png (3.19 KB, 下载次数: 0)

2015-4-10 14:49 上传

再接下来,保存当前函数的栈底到ESP所指向内存中去。

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

06.png (3.18 KB, 下载次数: 0)

2015-4-10 14:50 上传

提升栈底,此时 ESP 和EBP都指向相同地址

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

07.png (3.46 KB, 下载次数: 0)

2015-4-10 14:53 上传

int fun1(int a, int b)

{

int c = a + b;

int d = 0;

d = fun2(a, b, c);

return d;

}复制代码ESP减8, 我们已经为fun1的两个形参分配完了内存空间。通过代码我们看到,fun1中有两个局部变量c和d 。所以ESP减8的目的就是为我们的局部变量分配空间b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

08.png (3.81 KB, 下载次数: 0)

2015-4-10 14:58 上传

而在fun1函数中我们又调用了fun2函数,fun2函数的调用过程与fun1类似

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

09.png (3.94 KB, 下载次数: 0)

2015-4-10 15:00 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

010.png (3.93 KB, 下载次数: 0)

2015-4-10 15:00 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

011.png (4.63 KB, 下载次数: 0)

2015-4-10 15:00 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

012.png (4.68 KB, 下载次数: 1)

2015-4-10 15:00 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

013.png (4.87 KB, 下载次数: 0)

2015-4-10 15:01 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

014.png (5.26 KB, 下载次数: 0)

2015-4-10 15:01 上传

在fun2函数中,k运算得80; 函数返回语句 return k;我们要返回k的值,可是当我们退出函数后,显然fun2的栈空间已经不再有效,那么他是怎么把这个80传递到fun1中去的呢

计算机中存储数据的不仅仅有内存。CPU中也有若干用来存储数据的空间,称之为寄存器。所以在函数退出之前都会把结果保存到这些寄存器当中。32位CPU的通用寄存自然最多也只有32位了,64位CPU的通用寄存器最多也只有64位。所以函数参数传递通常为基本数据类型或指针。因为这些数据的宽度都没有超过寄存器的最大位宽。当我们向函数中传递一个结构体或类时,这个过程是一个数据复制的过程,如果结构体或类成员较多,复制过程肯定会消耗更多的空间和时间。当返回一个结构体或类对象时,同样会产生数据复制的过程。所以编程中通常是不会这样做的,而只需要传递一个结构体指针或类对象地址。只需要简单传递32位或64位的地址,一切问题都可以得到解决。提高程序运行效率,节省空间。这也是指针之所以强大高效的原因之一。

函数调用栈空间变化 我们已经基本了解了,接下来再看下,函数退出时栈的变化

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

015.png (5 KB, 下载次数: 0)

2015-4-10 15:15 上传

ESP-C 此时ESP和EBP指向相同地址,当前地址内存中存放的是前一个函数的EBP地址

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

016.png (5.1 KB, 下载次数: 0)

2015-4-10 15:17 上传

恢复原来EBP的数值

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

017.png (4.95 KB, 下载次数: 0)

2015-4-10 15:19 上传

恢复EIP的值为00401232 也就是告诉CPU回到调用fun2函数之前的函数中继续执行下一行代码

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

018.png (4.65 KB, 下载次数: 0)

2015-4-10 15:21 上传

彻底恢复到fun1的栈空间。 fun2的栈空间不再有效。 函数退出后原来使用的数值计算机并没有做多余的回收工作。just leave it alone.

所以你不应该返回一个函数局部变量的指针。当退出函数后这部分空间是不可控制的。因为在后边的代码很 可能又调用了其它函数,重新使用了fun2所使用过的栈空间。这时你用指针操作这块空间得到的是一个未知的数值。这个数值是不确定的。也就行成了野指针。还有局部变量的作用域也是由于这个原因。局部变量和参数只在当前函数内有效。退出后就无法也不应该再继续使用。

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

019.png (4.87 KB, 下载次数: 0)

2015-4-10 15:28 上传

还记得 大明湖畔的 return k吗?d = fun2(a, b, c);  相当于  d = k;

这个时候不再是把k的值复制给d了,fun2函数已经退出,同样他的栈空间不再有效。在 fun2退出时k的值是复制到了寄存器eax中的,所以这时的d的数值是从寄存器eax中取得的。

接下来fun1运行完成退出过程类似fun2

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

020.png (4.43 KB, 下载次数: 0)

2015-4-10 15:32 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

021.png (4.33 KB, 下载次数: 0)

2015-4-10 15:32 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

022.png (4.35 KB, 下载次数: 0)

2015-4-10 15:32 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

023.png (4.35 KB, 下载次数: 0)

2015-4-10 15:32 上传

b5b0b1954d7b1b86a4242bbd9d0c05eb.gif

024.png (4.09 KB, 下载次数: 1)

2015-4-10 15:32 上传

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值