# 堆和栈

首先堆和栈可以分为两种，一种是**数据结构**，另一种是和**内存的分配有关**，这两种虽然都有栈和堆，但是两者关系并不大，

## 4.1 数据结构中的堆栈以及队列

线性表是由n个相同类型的数据元素组成的有限序列，它是最基本的一种线性结构。顾名思义，**线性表就像是一条线，不会分叉**线性表有两种存储方式，分别是顺序存储和链式存储。==采用顺序存储的线性表称为顺序表，采用链式存储的线性表称为链表，链表在顺序表的基础上，增加了指向下一节点的地址，使得每个节点具有数据域和指针域==，链表又分为单链表、双向链表和循环链表。[具体参考](https://blog.csdn.net/WANGHAOXIN364/article/details/122909241)

### 4.1.1 堆和栈

堆和栈是一个东西，“堆栈” 其实**说的就是栈而不是堆**。 **栈**是一种特殊的线性表，其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶，另一端称为栈底。栈中的数据元素遵守：**后进先出**LIFO（Last In First Out）的原则（压子弹）。 **压栈：** 栈的插入操作叫做进栈/压栈/入栈，入数据在栈顶。 **出栈：** 栈的删除操作叫做出栈。出数据也在栈顶。

[具体参考](https://blog.csdn.net/chuxinchangcun/article/details/119896788)

### 4.1.2    队列

和栈相反，队列是一种**先进先出**的线性表。它只允许在表的一端进行插入，而在另一端删除元素，允许插入的一端称为队尾，允许删除的一端则称为队头。

## 4.2    内存模型中的堆栈

内存中的堆和栈实际都是在SARM上，从储存器地址上看，是从0X20000000到0X3FFFFFFF的512MB大小空间（F103ZET6实际只有0X20000000到0X2000FFFF的64KB在使用，剩余的地址等待使用）

堆(Heap)：人为申请手动释放，就是通过new、malloc、realloc分配的内存块，编译器不会负责它们的释放工作，需要用程序区释放。分配方式类似于数据结构中的链表。“内存泄漏”通常说的就是堆区。 栈（STACK）： 存放函数内的局部变量，形参和函数返回值。栈区之中的数据的作用范围过了之后，系统就会回收自动管理栈区的内存(分配内存 , 回收内存),不需要开发人员来手动管理。栈区就像是一家客栈，里面有很多房间，客人来了之后自动分配房间，房间里的客人可以变动，是一种动态的数据变动。[容易混淆堆栈？看完这篇，轻松区别堆与栈！ - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/315659027)

堆和栈都被包含在ZI-data数据段中

### 4.2.1    堆和栈的主要区别

* 1.**内存地址增长的方向不同**

  堆是向着内存地址增加的方向增长的，从**内存的低地址向高地址方向增长**；

  栈的增长方向与之相反，是向着内存地址减小的方向增长，**由内存的高地址向低地址方向增长**

  假设一个程序的函数调用顺序为：主函数main调用函数func1，函数func1调用函数func2。当这个程序被操作系统调入内存运行时，其对应的进程在内存中的映射结果如下图所示，显然func1的地址高于func2，如下图所示(注意代码段在FLASH上)

![](https://pic4.zhimg.com/80/v2-4d645f11ce5575a33cd798b81810fa07_720w.webp)

> 堆的生长方向,都是向上的.在程序里面,所有的内存分为:堆+栈. 只是他们各自的起始地址和增长方向不同,他们没有一个固定的界限,所以一旦堆栈冲突,系统就到了崩溃的时候了.

**2.申请的大小限制不同**

栈是向低地址扩展的数据结构，是一块连续的内存区域，栈顶的地址和栈的最大容量是系统预先规定好的，**能从栈获得的空间较小。**

堆是向高地址扩展的数据结构，是不连续的内存区域，堆获得的空间比较灵活，也比较大。

但实际以上的说法是在win端上，其他环境也不一定，以STM32F103ZET6为例，在官方提供的启动文件中Stack\_Size 为0x00000400，Heap\_Size为0x00000200，**也就是说栈1024字节，堆的大小为为512字节，栈的大小大于堆。**

> 因此若工程中使用的**局部变量较多，或嵌套关系复杂时**，定义的数据长度较大时，若不调整栈的空间大小，则会导致程序出现栈溢出，程序运行结果与预期的不符或程序跑飞。这时我们就需要手动的调整**栈的大小**。
>
> 当工程中使用了malloc动态分配内存空间时，这时分配的空间就为堆的空间。所以若默认的堆空间大小不满足工程需求时，就需要手动调整堆空间的大小。
>
> **调整方法：**
>
> 1 直接在启动文件中修改堆栈空间的大小

**3.申请效率不同**

栈由系统自动分配，速度快，但是程序员无法控制。

堆是有程序员自己分配，速度较慢，容易产生碎片，频繁执行malloc或free势必会造成内存空间的不连续，形成大量的碎片，使程序效率降低，不过用起来方便。

### 4.2.2    堆栈大小以及地址和堆栈指针地址的确定

用官方提供的启动文件时，打开map文件，到Local Symbols段的末尾，有如下信息，说明了msp指针指向地址0x20000788，堆HEAP区的大小为512字节，起始地址（堆向上增长，因此是最低地址）为0x20000188，栈STACK 区的大小为1024字节，起始地址（栈向下增长，因此是最高地址）为0x20000388

![](https://s2.loli.net/2023/02/24/mzF48gGiD1rTpOE.png)

###

### 4.2.3    堆栈增长方向的确定程序

如果不确定增长方向，可以用以下程序测试

```
//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;

//查找栈增长方向,结果保存在stack_dir里面.
void find_stack_direction(void)
{
    static u8 *addr=NULL; //用于存放第一个dummy的地址。
    u8 dummy;               //用于获取栈地址
    if(addr==NULL)    //第一次进入
    {                         
        addr=&dummy;     //保存dummy的地址
        find_stack_direction ();  //递归
    }
    else                //第二次进入
     {  
        if(&dummy>addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.
        else stack_dir=0;           //第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的. 
     }
}
```

## 4.3大小端模式

大端模式：地位字节存在高地址上，高位字节存在低地址上。\
小段模式：高位字节存在高地址上，低位字节存在低地址上。

stm32使用MDK编译时属于小端模式，简单的说，比如u32 temp=0x12345678;\
假设temp地址在0x2000 0010。那么在内存里面，存放就变成了：

| 地址          | hex         |
| ----------- | ----------- |
| 0x2000 0010 | 78 56 34 12 |

代码确认：

```
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;

//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
     int x=1;
     if(*(char*)&x==1)
         cpu_endian=0; //小端模式
     else cpu_endian=1;    //大端模式 
}
```

以\_\_initial\_sp的地址为例，0x20000798这个值是SP指针的值，应该写入0x08000000地址

![](https://s2.loli.net/2023/03/03/bUv93Ye7tkGiOPm.png)

使用调试器，在下方的地址查看栏输入0x08000000，发现下方的数据并不是0x20000798，而是反过来的，这是因为小端模式，高位字节存在高地址上，0x20000798的高位字节是20，而高地址是0x08000003，所以0x08000003地址上是20，以此类推。

[(62条消息) STM32 堆栈大小详解 以及变量存储位置\_allen6268198的博客-CSDN博客\_stm32栈空间一般分配多大](https://blog.csdn.net/allen6268198/article/details/91356377)
