C++ 中的栈(Stack)数据结构与堆的区别与内存布局(Stack vs Heap)

一、栈的基本概念

1. 定义

栈(Stack) 是一种 后进先出(LIFO, Last-In-First-Out) 的线性数据结构。

就像一摞盘子,后放的盘子在最上面,最先被取出。


二、C++ 中的栈实现方式

C++ 中主要有两种方式实现栈:

1. STL 容器适配器 std::stack

cpp 复制代码
#include <stack>
std::stack<int> s;

这是标准库中最常用的实现。

2. 自定义数组或链表实现

可以自己用 vectorlinked list 实现栈逻辑,常见于算法题或底层系统开发。


三、STL std::stack 概述

1. 定义

std::stack 是一个容器适配器 (container adapter) ,它在内部使用其他容器(如 deque, vector, list)来存储元素。

2. 默认实现

cpp 复制代码
template <class T, class Container = std::deque<T>>
class stack;

即默认底层容器是 std::deque<T>

3. 特性

  • 后进先出(LIFO)
  • 不支持随机访问
  • 只暴露 栈顶操作接口

四、常用成员函数详解

成员函数 功能 示例
push() 入栈 s.push(10);
pop() 出栈(删除栈顶元素) s.pop();
top() 访问栈顶元素 int x = s.top();
empty() 判断是否为空 if (s.empty())
size() 返回元素个数 s.size();
emplace() 原地构造并入栈 s.emplace(3, 4);
swap() 交换两个栈的内容 s1.swap(s2);

五、示例代码

cpp 复制代码
#include <iostream>
#include <stack>
using namespace std;

int main() {
    stack<int> s;

    // 入栈
    s.push(10);
    s.push(20);
    s.push(30);

    cout << "Stack top: " << s.top() << endl; // 输出 30

    // 出栈
    s.pop();
    cout << "After pop, top: " << s.top() << endl; // 输出 20

    cout << "Stack size: " << s.size() << endl;

    // 清空
    while (!s.empty()) {
        cout << "Popped: " << s.top() << endl;
        s.pop();
    }
}

输出:

复制代码
Stack top: 30
After pop, top: 20
Stack size: 2
Popped: 20
Popped: 10

六、底层原理分析

1. 适配器模式

std::stack 并不是一个独立的数据结构,而是对底层容器的封装:

cpp 复制代码
template<class T, class Container = deque<T>>
class stack {
protected:
    Container c;
public:
    bool empty() const { return c.empty(); }
    size_t size() const { return c.size(); }
    T& top() { return c.back(); }
    void push(const T& value) { c.push_back(value); }
    void pop() { c.pop_back(); }
};

2. 底层容器类型选择

容器类型 优点 缺点
deque 双端操作高效(默认) 占用稍多内存
vector 连续内存、快速访问 出栈扩容代价较高
list 插入删除快 内存碎片多、访问慢

七、内存与性能分析

操作 时间复杂度 说明
push O(1)* 平均常数时间(vector 可能扩容)
pop O(1) 删除尾部元素
top O(1) 直接访问尾部
empty / size O(1) 成员变量直接返回

八、应用场景

  1. 表达式求值

    • 中缀转后缀、逆波兰表达式计算
  2. 括号匹配

    • 判断括号是否成对出现
  3. 函数调用栈

    • 系统内部维护函数调用与返回
  4. 回溯算法

    • DFS 深度优先搜索
  5. 撤销(Undo)/恢复(Redo)操作


九、自定义栈实现

使用数组实现:

cpp 复制代码
#include <iostream>
using namespace std;

template<typename T, size_t N>
class Stack {
    T data[N];
    int topIndex = -1;

public:
    void push(const T& value) {
        if (topIndex >= N - 1) throw overflow_error("Stack overflow");
        data[++topIndex] = value;
    }

    void pop() {
        if (topIndex < 0) throw underflow_error("Stack underflow");
        --topIndex;
    }

    T top() const {
        if (topIndex < 0) throw underflow_error("Empty stack");
        return data[topIndex];
    }

    bool empty() const { return topIndex < 0; }
    size_t size() const { return topIndex + 1; }
};

十、系统层面理解:调用栈(Call Stack)

除了 STL 数据结构,还是系统内存的一部分。

类型 区域 功能
栈区 (Stack) 由编译器自动分配 存储局部变量、函数参数、返回地址
堆区 (Heap) 由程序员手动分配 存储动态对象(new / malloc)

函数调用时,系统自动使用**调用栈(call stack)**来保存返回地址和局部变量。


十一、堆栈的区别与内存布局(Stack vs Heap)


1、基本概念

名称 中文 作用 分配方式
Stack 管理函数调用、局部变量、返回地址等 由系统自动分配 / 释放
Heap 动态内存存储,由程序员控制 由程序员手动分配 / 释放(new/deletemalloc/free

2、从内存布局看区别

以典型的 C/C++ 程序在 Linux 内存布局为例(自低地址到高地址):

复制代码
+---------------------------+  高地址
|      栈 Stack             |  函数调用栈、局部变量
|      ↓(向下增长)       |
+---------------------------+
|      堆 Heap              |  new / malloc 动态分配(向上增长)
+---------------------------+
|      BSS 段               |  未初始化的全局/静态变量
+---------------------------+
|      数据段 Data          |  已初始化的全局/静态变量
+---------------------------+
|      代码段 Text          |  程序指令、常量
+---------------------------+
                            低地址

特点:

  • 栈向下增长(地址递减)
  • 堆向上增长(地址递增)
  • 二者中间的空间为系统保留的内存或动态共享库区

3、分配与释放机制

比较项 栈 Stack 堆 Heap
分配方式 编译器自动分配 程序员手动分配
释放方式 离开作用域自动释放 需要显式释放(delete/free
分配时间 编译期或运行时由编译器管理 运行时动态分配
分配效率 快(简单移动栈顶指针) 慢(涉及操作系统堆管理)
内存碎片 不会产生碎片 易产生碎片
空间大小 一般较小(几 MB) 受系统总内存限制(可达 GB)
异常风险 栈溢出(Stack Overflow) 内存泄漏(Memory Leak)
典型使用 局部变量、函数参数 动态数组、对象、容器(如 vector)

4、实例分析

栈分配示例:

cpp 复制代码
void func() {
    int a = 10;   // 分配在栈上
    double b = 3.14;
}

函数调用时,编译器为 ab 分配栈空间;

函数返回时,这部分内存自动释放。


堆分配示例:

cpp 复制代码
void func() {
    int* p = new int(10);   // 分配在堆上
    delete p;               // 手动释放
}
  • new 调用堆管理器(malloc 系统调用)分配空间
  • delete 调用堆释放函数(free)
  • 如果忘记 delete,就会造成 内存泄漏

5、内存使用示意图

cpp 复制代码
#include <iostream>
using namespace std;

int g = 0; // 全局变量 → 数据段

int main() {
    int a = 1;                 // 栈变量
    int* p = new int(2);       // 堆变量
    static int s = 3;          // 静态变量 → 数据段
    cout << "stack a: " << &a << endl;
    cout << "heap  p: " << p << endl;
    cout << "static s: " << &s << endl;
    cout << "global g: " << &g << endl;
    delete p;
}

输出示例(地址大致关系):

复制代码
stack a: 0x7ffeeff8
heap  p: 0x600010
static s: 0x60104c
global g: 0x601040

可见:

  • 栈区地址较高
  • 堆区地址较低
  • 全局/静态变量在固定的 Data 段
  • 程序代码在 Text 段中

6、性能与风险比较

特性 栈 Stack 堆 Heap
性能 快,连续内存,一次性分配 慢,需查找空闲块、更新堆结构
安全性 自动释放,易管理 容易泄漏、悬垂指针
可变性 空间有限 可动态扩展
典型错误 Stack Overflow Memory Leak / Use-after-free

7、C++ 中的内存管理建议

  1. 优先使用栈对象

    • 自动管理生命周期,不会泄漏。

    • 如:

      cpp 复制代码
      std::string s = "hello"; // 栈上管理,内部堆分配自动释放
  2. 动态分配时使用智能指针

    • 避免手动 delete
    cpp 复制代码
    #include <memory>
    auto p = std::make_shared<int>(42);
  3. 不要返回局部变量地址

    cpp 复制代码
    int* foo() {
        int x = 10;
        return &x;  //  栈变量函数返回后已销毁
    }
  4. 避免频繁分配/释放堆内存

    • 建议使用内存池(memory pool)或对象池优化。

8、栈溢出与堆溢出

栈溢出 (Stack Overflow)

原因:函数递归过深或局部数组太大。

cpp 复制代码
void recurse() { recurse(); } // 无限递归 → Stack Overflow

堆溢出 (Heap Overflow)

原因:动态内存越界写入。

cpp 复制代码
int* p = new int[2];
p[5] = 10; // 越界写入 → Heap Corruption

9、程序运行生命周期内存演化图

复制代码
程序加载时:
 ┌──────────────────────┐
 │ 代码段(text)       │  ← 程序指令
 │ 数据段(data + bss) │  ← 全局/静态变量
 │ 堆(heap)           │  ← new/malloc 动态分配
 │ ...(空闲空间)...   │
 │ 栈(stack)          │  ← 函数调用栈帧
 └──────────────────────┘
  • 程序执行时,栈不断增长/收缩;
  • 堆在运行中动态申请/释放;
  • 数据段和代码段在整个程序生命周期中固定存在。

10、总结对比表

特征 栈 (Stack) 堆 (Heap)
分配方式 系统自动 程序员控制
生命周期 函数结束自动释放 必须手动释放
地址增长方向 向下(高→低) 向上(低→高)
管理开销
空间大小 较小(默认几 MB) 较大(受内存限制)
容易出错类型 栈溢出 内存泄漏、野指针
推荐用法 局部变量、临时对象 大对象、动态容器、跨函数数据

"栈快且短,堆大但慢;栈自动管,堆需自担。"

相关推荐
码界奇点2 小时前
Linux进程间通信三System V 共享内存完全指南原理系统调用与 C 封装实现
linux·c语言·网络·c++·ux·risc-v
枫子有风2 小时前
【go.sixue.work】2.2 面向对象:接口与多态
开发语言·后端·golang·xcode
小无名呀2 小时前
tcp_Calculator(自定义协议,序列化,反序列化)
网络·c++·网络协议·tcp
AA陈超2 小时前
ASC学习笔记0001:处理目标选择系统中当Actor拒绝目标确认时的调用
c++·笔记·学习·游戏·ue5·游戏引擎·虚幻
前端小L3 小时前
图论专题(六):“隐式图”的登场!DFS/BFS 攻克「岛屿数量」
数据结构·算法·深度优先·图论·宽度优先
-大头.3 小时前
Python数据结构之旅:09-图论基础——连接万物的网络
数据结构·图论
..过云雨3 小时前
13.【Linux系统编程】从ELF格式深入理解动静态库
linux·c语言·c++·后端
长沙红胖子Qt3 小时前
QGIS开发笔记(五):qgis加载标记点功能,基础标记数量与性能对比测试
c++
zz0723203 小时前
数据结构 —— 队列
数据结构