没有合适的资源?快使用搜索试试~ 我知道了~
04深入理解栈:从CPU和函数的视角看栈的管理1
需积分: 0 0 下载量 90 浏览量
2022-08-03
22:57:12
上传
评论
收藏 9.82MB PDF 举报
温馨提示
试读
20页
04 | 深入理解栈:从CPU和函数的视角看栈的管理所以,今天这节课,我们继续深入探讨一下栈这个话题,我会带你基于“符合人的直观思维”,也就是函数的层面和 CP
资源详情
资源评论
资源推荐
2021/11/7 04 | 深入理解栈:从CPU和函数的视角看栈的管理
https://time.geekbang.org/column/article/433530 1/20
04 | 深入理解栈:从CPU和函数的视角看栈的管理
2021-11-01 海纳
《编程高手必学的内存知识》
课程介绍
讲述:海纳
时长 20:03 大小 18.38M
你好,我是海纳。
上节课,我们讲到,栈被操作系统安排在进程的高地址处,它是向下增长的。但这只是对
栈相关知识的“浅尝辄止”。那我们今天这节课,就会跟着前面的脉络,让你可以更深刻
地理解栈的运行原理。
栈是每一个程序员都很熟悉的话题,但你敢说你真的完全了解它吗?我相信,你在工作中
肯定遇到过栈溢出(StackOverflow)的错误,比如在写递归函数的时候,当漏掉退出条
件,或者退出条件不小心写错了,就会出现栈溢出错误。我们也经常听说缓冲区溢出带来
的严重的安全问题,这在日常的工作中都是要避免的。
下载APP
2021/11/7 04 | 深入理解栈:从CPU和函数的视角看栈的管理
https://time.geekbang.org/column/article/433530 2/20
所以,今天这节课,我们继续深入探讨一下栈这个话题,我会带你基于“符合人的直观思
维”,也就是函数的层面和 CPU 的机器指令层面,多角度来理解栈相关的概念。这样,你
以后遇到与栈相关的问题的时候,才知道如何着手进行排查。最后,我们还会通过一个缓
冲区溢出攻击栈的案例,看看我们在日常工作中如何提升代码的健壮度和安全性。
函数与栈帧
当我们在调用一个函数的时候,CPU 会在栈空间(这当然是线性空间的一部分)里开辟一
小块区域,这个函数的局部变量都在这一小块区域里存活。当函数调用结束的时候,这一
小块区域里的局部变量就会被回收。
这一小块区域很像一个框子,所以大家就命名它为 stack frame。frame 本意是框子的意
思,在翻译的时候被译为帧,现在它的中文名字就是栈帧了。
所以,我们可以说,栈帧本质上是一个函数的活动记录。当某个函数正在执行时,它的活
动记录就会存在,当函数执行结束时,活动记录也被销毁。
不过,你要注意的是,在一个函数执行的时候,它可以调用其他函数,这个时候它的栈帧
还是存在的。例如,A 函数调用 B 函数,此时 A 的栈帧不会被销毁,而是会在 A 栈帧的下
方,再去创建 B 函数的栈帧。只有当 B 函数执行完了,B 的栈帧也被销毁了,CPU 才会回
到 A 的栈帧里继续执行。
我们举个例子说明一下,就很好理解了。你可以看一下这个代码:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
void swap(int a, int b) {
int t = a;
a = b;
b = t;
}
void main() {
int a = 2;
int b = 3;
swap(a, b);
printf("a is %d, b is %d\n", a, b);
}
2021/11/7 04 | 深入理解栈:从CPU和函数的视角看栈的管理
https://time.geekbang.org/column/article/433530 3/20
你可以看到,在 swap 函数中,a 和 b 的值做了一次交换,但是在 main 函数里,打印 a
和 b 的值,a 还是 2,b 还是 3。这是为什么呢?从栈帧的角度,这个问题就非常容易理
解:
在 main 函数执行的时候,main 的栈帧里存在变量 a 和 b。当 main 在调用 swap 方法的
时候,会在 main 的帧下面新建 swap 的栈帧。swap 的帧里也有局部变量 a 和 b,但是明
显这个 a、b 与 main 函数里的 a、b 没有任何关系,不管对 swap 的帧里的 a/b 变量做任
何操作都不会影响 main 函数的栈帧。
接下来,我们再通过一个递归的例子来加深对栈的理解。由于递归执行的过程会出现函数
自己调用自己的情况,也就是说,一个函数会对应多个同时活跃的记录(即栈帧)。所
以,理解了递归函数的执行过程,我们就能更加深刻地理解栈帧与函数的关系。
当我们在谈递归时,我们在谈什么
我们先看一下最经典的递归问题:汉诺塔。汉诺塔问题是这样描述的:有三根柱子,记为
A、B、C,其中 A 柱子上有 n 个盘子,从上到下的编号依次为 1 到 n,且上面的盘子一定
比下面的盘子小。要求一次只能移动一只盘子,且大的盘子不能压在小的盘子上,那么将
所有盘子从 A 移到 C 总共需要多少步?
这道题的详细分析过程是一种递归推导的过程,不是我们这节课的重点,如果你对解法感
兴趣的话,可以自己查找相关资料。我们这里,重点来讲解递归程序执行的过程中,栈是
怎么样变化的,这样可以帮助我们理解栈的基本工作原理。
2021/11/7 04 | 深入理解栈:从CPU和函数的视角看栈的管理
https://time.geekbang.org/column/article/433530 4/20
你先看一下汉诺塔问题的求解程序:
这段代码可以打印出借由 B 柱子将 5 个盘子从 A 搬移到 C 的所有步骤。这个的核心是
hanoi 函数,在深入分析代码的执行过程之前,我们可以先从符合直观思维的角度尝试理
解 hanoi 函数。
hanoi 函数有四个参数。第一个 src 代表要搬的起始柱子(开始时是 A),第二个代表目
标柱子(开始时是 C),第三个代表可以借用的中间的那个柱子(开始时是 B),第四个
参数代表一共要搬的盘子总数(开始时是 5)。
代码的第 13 行的意义是,如果要从 A 搬 5 个盘子到 C,可以先将 4 个盘子搬到 B 上,然
后第 14 行代表将第 5 个盘子从 A 搬到 C,第 15 行代表把 B 上面的 4 个盘子搬到 C 上
去。第 8 行的判断是说当只搬一个盘子的时候,就可以直接调用 move 方法。
以上就是递归程序的设计思路。下面我们再具体分析这个代码的执行过程。为了简便起
见,我们选择 n=3 进行分析。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
void move(char src, char dst, int n) {
printf("move plate %d form %c to %c\n", n, src, dst);
}
void hanoi(char src, char dst, char aux, int n) {
if (n == 1) {
move(src, dst, 1);
return;
}
hanoi(src, aux, dst, n-1);
move(src, dst, n);
hanoi(aux, dst, src, n-1);
}
int main() {
hanoi('A', 'C', 'B', 5);
}
剩余19页未读,继续阅读
番皂泡
- 粉丝: 21
- 资源: 320
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
评论0