指针与栈:用指针模拟栈的入栈出栈操作

栈这种数据结构,说白了就是「后进先出」。你想想看,往一个桶里放盘子,最后放进去的总是最先被拿出来。我在嵌入式开发中,栈用得特别多——函数调用、中断现场保护、局部变量存储,底层全是栈在干活。

但今天咱们不聊硬件栈,而是用指针自己搭一个栈。为什么要这么做?因为理解了指针怎么操作栈,你才能真正明白内存是怎么被「管」起来的。

栈的基本结构

一个栈需要三个核心要素:

  • 一块连续的内存区域——用来存数据
  • 一个栈顶指针——指向当前栈顶位置
  • 一个栈底指针——标记栈的起始位置

我个人习惯用数组来模拟这块内存,然后用一个指针变量来充当栈顶指针。这样既直观,又方便调试。

核心思路:栈顶指针永远指向下一个空闲位置(或者当前栈顶元素),入栈时指针上移,出栈时指针下移。

用指针实现入栈操作

入栈,就是把数据压到栈顶。用指针做这件事,其实就是:

  1. 检查栈是否已满
  2. 把数据写入栈顶指针指向的位置
  3. 栈顶指针向上移动
#include <stdio.h>
#include <stdbool.h>

#define STACK_SIZE 10

int stack[STACK_SIZE];
int *top = stack;  // 栈顶指针,初始指向栈底

bool push(int value) {
    // 检查栈是否已满
    if (top >= stack + STACK_SIZE) {
        printf("栈已满,无法入栈\n");
        return false;
    }
    
    *top = value;   // 写入数据
    top++;          // 栈顶指针上移
    return true;
}

这里有个细节:top++ 之后,top 指向的是下一个空闲位置,而不是当前元素。我在项目中遇到过这种设计带来的坑——如果你不小心把 top 当作当前元素来读,读到的就是未初始化的垃圾值。

我的习惯:入栈时先赋值再移动指针,出栈时先移动指针再取值。这样栈顶指针始终指向「下一个可用位置」,逻辑上更统一。

用指针实现出栈操作

出栈是入栈的逆过程:

  1. 检查栈是否为空
  2. 栈顶指针向下移动
  3. 取出栈顶指针指向的数据
int pop(void) {
    // 检查栈是否为空
    if (top == stack) {
        printf("栈已空,无法出栈\n");
        return -1;  // 实际项目中建议用错误码或传参返回
    }
    
    top--;          // 栈顶指针下移
    return *top;    // 取出数据
}

嗯,这里要注意:出栈时我们并没有真正「删除」数据,只是把栈顶指针往下挪了。下次入栈时,新数据会直接覆盖旧数据。这种做法在嵌入式系统中很常见——省时间、省指令。

完整示例:入栈出栈演示

int main() {
    // 入栈测试
    push(10);
    push(20);
    push(30);
    
    // 出栈测试
    printf("出栈: %d\n", pop());  // 30
    printf("出栈: %d\n", pop());  // 20
    printf("出栈: %d\n", pop());  // 10
    
    return 0;
}

运行结果:

出栈: 30
出栈: 20
出栈: 10

你看,后入栈的 30 先出来,这就是栈的特性。

栈的常见状态检查

写栈代码时,我最怕的就是「栈溢出」和「空栈读取」。这两个问题在嵌入式环境里尤其致命——栈溢出可能直接覆盖掉其他变量,空栈读取则可能读到随机值。

所以,我建议你封装两个检查函数:

bool is_empty(void) {
    return top == stack;
}

bool is_full(void) {
    return top >= stack + STACK_SIZE;
}

有了这两个函数,入栈出栈时先做检查,代码就安全多了。

知识体系图

指针模拟栈的核心逻辑 stack[0] stack[1] stack[2] 栈顶 空闲 空闲 栈内存区域 top 指针 入栈 (push) 出栈 (pop) ① 检查是否满 ② *top = value ③ top++ ① 检查是否空 ② top-- ③ return *top 栈顶指针 top 始终指向下一个空闲位置(或当前栈顶元素的上方) 入栈:先写后移 | 出栈:先移后读

避坑指南

我曾经犯过的错:有一次在 STM32 上写栈操作,忘了检查栈是否为空。结果在中断服务函数里连续 pop 了两次,第二次读到了栈底之外的数据,直接导致系统跑飞。从那以后,我所有的栈操作都强制加检查——哪怕只是原型代码。

另外,用指针模拟栈时,指针的初始化和边界判断一定要做对。我见过不少新手把 top = &stack[0] 写成 top = stack,虽然效果一样,但后者更符合指针的语义——数组名本身就是首地址。

小结

用指针模拟栈,本质上就是控制一个指针在数组里上下移动。入栈时指针上移,出栈时指针下移。你只要记住三点:

  • 栈顶指针的初始位置
  • 入栈出栈时指针的移动方向
  • 边界检查不能省

掌握了这些,你就能用指针轻松搭建一个栈。下次遇到需要后进先出的场景——比如撤销操作、表达式求值、函数调用模拟——你都可以用这套思路来解决。

一个小练习:试试把上面的代码改成「栈顶指针指向当前元素」的版本。你会发现入栈时要先移动指针再赋值,出栈时要先取值再移动指针。两种方式都能用,选一种你习惯的就好。


公众号:蓝海资料掘金营,微信deep3321