
| 操作名称 | 英文术语 | 它的作用 | 现实比喻 |
|---|---|---|---|
| 入栈 | Push |
把数据放到栈顶 | 往箱子里放进一本书 |
| 出栈 | Pop |
把栈顶的数据扔掉 | 把箱子最上面那本书拿走 |
| 取栈顶 | Top / Peek |
只看一眼栈顶的数据,不扔 | 看看箱子最上面那本书的书名 |
| 判空 | Empty |
检查栈里还有没有数据 | 看看箱子是不是空的 |
| 求大小 | Size |
看看栈里一共有几个数据 | 数数箱子里一共几本书 |
"既然栈的底层是个数组,那我为什么不直接用
arr[2]、arr[5]去随便读取修改中间的数据呢?为什么要死死限制住,只允许在'栈顶'这一个地方进出呢?"
答案是:限制即是特征(Restriction is a Feature) 。
可以说 学习栈 前置条件是动态数组 在限制 或者新增一些功能来实现复杂
更多功能,完成特定需求的数据结构
1 从头文件开始 stack.h

-
关于
top = 0:你敏锐地发现了top的值恰好就是有效个数 。因为数组下标是从 0 开始的,所以当top指向下标n(也就是下一个空位置)的时候,前面正好塞满了n个有效数据。 -
关于
top = -1:你抓住了"下标与个数差 1"的核心特性。既然top死死盯着最后一个有效数据,那数据的总个数自然就是top + 1,反过来说也就是:当前指向的下标 = 有效个数 - 1。
我们通常不用纠结它具体落在元素内部哪个字节,只需要知道它保存首地址,并且它的运算遵循"按类型步长移动"的规则。
这是一个非常直击本质的好问题!在 C/C++ 的内存管理中,理解这两行代码的先后关系,就等于掌握了防范内存泄漏和野指针的终极法则。
为了把这个讲透,我们需要分清两个概念:"内存空间" 和 "记录地址的指针(小纸条)"。
我们用"退订酒店房间"来打个比方。
第一步:free(pst->arr); ------ 退还房间(释放内存)
-
它的作用 :
free的工作是告诉操作系统(酒店前台):"这块动态申请的内存(酒店房间),我用完了,现在还给你,你可以把它租给别人了。" -
背后的真相 :
free只会 释放那块内存空间,但它绝对不会 去修改pst->arr这个变量里的值。 -
危险诞生 :执行完
free之后,pst->arr里面依然保存着刚才那个房间的门牌号(内存地址)!此时的pst->arr就变成了一个野指针(悬空指针 Dangling Pointer)。这就像是你虽然退了房,但手里还偷偷捏着写有房间号的小纸条。
第二步:pst->arr = NULL; ------ 烧毁小纸条(指针置空)
-
它的作用 :把
pst->arr的值改成NULL(地址0x0,表示"哪儿也不指")。 -
为什么要这么做 :既然房间已经退了,如果后续代码不小心又写了一句
pst->arr[0] = 10;(按照纸条上的房间号跑回去放东西),程序就会立刻崩溃,因为你侵入了不属于你的系统内存。 -
安全保障 :如果把它置为
NULL,在 C 语言中,对NULL指针进行读写操作,程序会稳定地报出"空指针异常(段错误)"并立即停止。这比"悄悄修改了别人的内存却不知道"要安全、好排查得多。这相当于你退房后,把写着房间号的小纸条烧毁了,彻底断绝了走错房间的可能性。
🌟 核心反问:如果把顺序反过来会怎样?
这是理解先后顺序的最佳方式。假设我们先写第二步,再写第一步:
C
// ❌ 灾难性的错误写法
pst->arr = NULL; // 1. 先把地址清空(烧毁小纸条)
free(pst->arr); // 2. 然后再去释放内存(去前台退房)
会发生什么?
-
你先执行了
pst->arr = NULL;,此时你彻底忘记了当初申请的那块内存在哪(弄丢了房间号)。 -
接着执行
free(pst->arr);,由于此时pst->arr已经是NULL了,相当于你执行了free(NULL);。在 C 语言标准中,free(NULL)是一个空操作,什么都不会发生。 -
结果:内存泄漏(Memory Leak)。 那块真正的内存空间永远留在了堆区,操作系统以为你还在用,你却再也找不到它了,直到你的程序结束,这块内存都被白白浪费着。
总结
-
必须先
free:因为必须用指针里存的地址去找到那块内存,把它还给系统。 -
必须后
NULL:因为还给系统后,那个地址就成了"毒药",必须马上清空指针,防止以后误食。
⏳ free(pst->arr) 执行的前后,到底发生了什么?
状态一:free 之前(岁月静好)
假设你在程序里写了 pst->arr = (int*)malloc(16);。
-
你的口袋里(变量所在处): 有一张小纸条,名字叫
pst->arr,上面写着"储物柜编号:0x112233"。 -
储物柜(堆区内存):
0x112233号柜子上挂着一把锁,系统(操作系统/C标准库)的账本上写着:"0x112233柜子,已被当前程序租用。" -
你可以随时拿着纸条,去打开这个柜子存取数据。
状态二:执行 free(pst->arr) 的那一瞬间(只管杀)
当你调用这个函数时,系统底层(比如 Linux 下的 ptmalloc 内存管理器)会做以下几件事:
-
它顺着你提供的
0x112233编号,找到那个柜子。 -
它在自己的系统账本 上,把"0x112233"的状态从"已分配"划掉,改写为"空闲 (Free)"。
-
柜子上的锁被系统收回了,柜子变成了无主状态,随时可以租给下一个来申请内存的程序。
状态三:free 之后的第一秒(悬空诞生 / 不管埋)
重点来了!系统在做完上面那些事之后,直接拍拍屁股走人了。
它绝对不会做以下两件事(这就是所谓的"不管埋"):
-
不清理柜子里的东西: 柜子里原来存的数字(比如 10, 20),通常还躺在里面,直到被下一个租客覆盖(这就叫内存里的"脏数据")。
-
不没收你的小纸条: 你的变量
pst->arr里面,依然清清楚楚地写着"0x112233"!
✨ 悬空指针(Dangling Pointer)在此刻正式诞生:
纸条(指针)还是那张纸条,上面的编号也毫无变化,但它指向的那个物理实体(内存归属权)已经不属于你了。你拿着一张旧纸条,指着一个已经被系统收回的储物柜,这就是"悬空"。
如果在 free 之后你没有执行 pst->arr = NULL;,那么在后续的代码中,如果你(或者别人)不小心写了一句 pst->arr[0] = 100;:
这就相当于你按照旧纸条上的地址,强行撬开了一个可能已经租给别人的柜子,并往里面塞了东西。系统一旦发现,就会毫不留情地把你的程序"当场击毙"(报出 Segmentation Fault 段错误)。
所以,pst->arr = NULL; 就是在"杀"完之后,亲手把那张作废的纸条烧掉。 这样哪怕以后误操作,也找不到那个柜子了,系统一看你是去试图打开 NULL (编号0的绝对禁区),会直接拦住你,而不会破坏别人的数据。