痞子衡嵌入式:嵌入式里堆栈原理及其纯C实现

时间:2023-12-27 14:48:07

  大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家讲的是嵌入式里堆栈原理及其纯C实现

  今天给大家分享的这篇还是2016年之前痞子衡写的技术文档,花了点时间重新编排了一下格式。栈这种结构在嵌入式里其实是非常常用的,比如函数调用与返回就是典型的栈应用,虽然很多时候栈都是CPU系统在自动管理,我们只需要在链接文件里分配栈大小以及栈存放位置,但稍微了解一下栈的原理会更加利于我们去理解嵌入式代码执行机制,以及帮助我们进一步去调试。

1.何为堆栈?

  堆HEAP与栈STACK是两个不同概念,其本质上都是一种数据结构。

  栈是一种按数据项排列的数据结构,只能在一端(栈顶top)对数据项进行插入和删除,其符合后进先出(Last-In / First-Out)原则。栈(os)一般是由编译器自动分配释放,其使用的是一级缓存。

  堆也是一种分配方式类似于链表的数据结构,其可以在任意位置对数据项进行操作。堆(os)一般由程序员手动分配释放,其使用的是二级缓存。

  在嵌入式世界里,堆栈一般指的仅是栈。

2.作用与意义

  在MCU中,栈这种结构一般被cpu和os所使用。

  在cpu裸机中使用情况分两种:一、主动进行函数调用时,STACK用以暂存下一条指令地址、函数参数、函数中定义的局部变量;二、硬中断来临时,暂存当前执行的现场数据(下一条指令地址、各种缓存数据),中断结束后,用以恢复。

  在os中使用时,硬栈的使用同cpu裸机;但os一般会为每个任务额外分配一个软栈,在任务调度时,可用软中断打断当前正在执行的任务,栈则用以保存各自任务以恢复。

3.软硬之分

  硬件堆栈:是通过寄存器SP作为索引指针的地址,是调用了BL等函数调用指令后硬件自动填充的堆栈。

  软件堆栈:是编译器为了处理一些参数传递而做的堆栈,会由编译器自动产生和处理,可以通过相应的编译选项对其进行编辑。

  简单一点说,硬件堆栈主要做为地址堆栈用,而软件堆栈主要会被分配成数据堆栈。或看其栈顶指针是否和CPU具有特殊的关联,有关联者(如SP)“硬”,而无关联者“软”。

4.栈的纯C实现

  基本的抽象数据类型(ADT)是编写C程序必要的过程,这类ADT有链表、堆栈、队列和树等,本节主要讲解下堆栈的几种实现方法以及他们的优缺点。

  堆栈(stack)的显著特点是后进先出(Last-In First-Out, LIFO),其实现的方法有三种可选方案:静态数组、动态分配的数组、动态分配的链式结构。

静态数组:特点是要求结构的长度固定,而且长度在编译时候就得确定。其优点是结构简单,实现起来方便而不容易出错。而缺点就是不够灵活以及固定长度不容易控制,适用于知道明确长度的场合。

动态数组:特点是长度可以在运行时候才确定以及可以更改原来数组的长度。优点是灵活,缺点是由此会增加程序的复杂性。

链式结构:特点是无长度上线,需要的时候再申请分配内存空间,可最大程度上实现灵活性。缺点是链式结构的链接字段需要消耗一定的内存,在链式结构中访问一个特定元素的效率不如数组。

  首先先确定一个堆栈接口的头文件,里面包含了各个方案下的函数原型,放在一起是为了实现程序的模块化以及便于修改。然后再接着分别介绍各个方案的具体实施方法。

  堆栈接口stack.h文件代码:

1./* 
2.** 堆栈模块的接口 stack.h 
3.*/  
4.#include<stdlib.h>  
5.  
6.#define STACK_TYPE int /* 堆栈所存储的值的数据类型 */  
7.  
8./* 
9.** 函数原型:create_stack 
10.** 创建堆栈,参数指定堆栈可以保存多少个元素。 
11.** 注意:此函数只适用于动态分配数组形式的堆栈。 
12.*/  
13.void create_stack(size_t size);  
14.  
15./* 
16.** 函数原型:destroy_stack 
17.** 销毁一个堆栈,释放堆栈所适用的内存。 
18.** 注意:此函数只适用于动态分配数组和链式结构的堆栈。 
19.*/  
20.void destroy_stack(void);  
21.  
22./* 
23.** 函数原型:push 
24.** 将一个新值压入堆栈中,参数是被压入的值。 
25.*/  
26.void push(STACK_TYPE value);  
27.  
28./* 
29.** 函数原型:pop 
30.** 弹出堆栈中栈顶的一个值,并丢弃。 
31.*/  
32.void pop(void);  
33.  
34./* 
35.** 函数原型:top 
36.** 返回堆栈顶部元素的值,但不改变堆栈结构。 
37.*/  
38.STACK_TYPE top(void);  
39.  
40./* 
41.** 函数原型:is_empty 
42.** 如果堆栈为空,返回TRUE,否则返回FALSE。 
43.*/  
44.int is_empty(void);  
45.  
46./* 
47.** 函数原型:is_full 
48.** 如果堆栈为满,返回TRUE,否则返回FALSE。 
49.*/  
50.int is_full(void);  

4.1 静态数组

  在静态数组堆栈中,STACK_SIZE表示堆栈所能存储的元素的最大值,用top_element作为数组下标来表示堆栈里面的元素,当top_element == -1的时候表示堆栈为空;当top_element == STACK_SIZE - 1的时候表示堆栈为满。push的时候top_element加1,top_element == 0时表示第一个堆栈元素;pop的时候top_element减1。

  a_stack.c 源代码如下:

1./* 
2.**  
3.** 静态数组实现堆栈程序 a_stack.c ,数组长度由#define确定 
4.*/  
5.  
6.#include"stack.h"  
7.#include<assert.h>  
8.#include<stdio.h>  
9.  
10.#define STACK_SIZE 100 /* 堆栈最大容纳元素数量 */  
11.  
12./* 
13.** 存储堆栈中的数组和一个指向堆栈顶部元素的指针 
14.*/  
15.static STACK_TYPE stack[STACK_SIZE];  
16.static int top_element = -1;  
17.  
18./* push */  
19.void push(STACK_TYPE value)  
20.{  
21.    assert(!is_full()); /* 压入堆栈之前先判断是否堆栈已满*/  
22.    top_element += 1;  
23.    stack[top_element] = value;  
24.}  
25.  
26./* pop */  
27.void pop(void)  
28.{  
29.    assert(!is_empty()); /* 弹出堆栈之前先判断是否堆栈已空 */  
30.    top_element -= 1;  
31.}  
32.  
33./* top */  
34.STACK_TYPE top(void)  
35.{  
36.    assert(!is_empty());  
37.    return stack[top_element];  
38.}  
39.  
40./* is_empty */  
41.int is_empty(void)  
42.{  
43.    return top_element == -1;  
44.}  
45.  
46./* is_full */  
47.int is_full(void)  
48.{  
49.    return top_element == STACK_SIZE - 1;  
50.}  

4.2 动态数组

  头文件还是用stack.h,改动的并不是很多,增加了stack_size变量取代STACK_SIZE来保存堆栈的长度,数组由一个指针来代替,在全局变量下缺省为0。

  create_stack函数首先检查堆栈是否已经创建,然后才分配所需数量的内存并检查分配是否成功。destroy_stack函数首先检查堆栈是否存在,已经释放内存之后把长度和指针变量重新设置为零。is_empty 和 is_full 函数中添加了一条断言,防止任何堆栈函数在堆栈被创建之前就被调用。

  d_stack.c源代码如下:

1./* 
2.** 动态分配数组实现的堆栈程序 d_stack.c 
3.** 堆栈的长度在创建堆栈的函数被调用时候给出,该函数必须在任何其他操作堆栈的函数被调用之前条用。 
4.*/  
5.#include"stack.h"  
6.#include<stdio.h>  
7.#include<malloc.h>  
8.#include<assert.h>  
9.  
10./* 
11.** 用于存储堆栈元素的数组和指向堆栈顶部元素的指针 
12.*/  
13.static STACK_TYPE *stack;  
14.static int        stack_size;  
15.static int        top_element = -1;  
16.  
17./* create_stack */  
18.void create_stack(size_t size)  
19.{  
20.    assert(stack_size == 0);  
21.    stack_size = size;  
22.    stack = (STACK_TYPE *)malloc(stack_size * sizeof(STACK_TYPE));  
23.    if(stack == NULL)  
24.        perror("malloc分配失败");  
25.}  
26.  
27./* destroy */  
28.void destroy_stack(void)  
29.{  
30.    assert(stack_size > 0);  
31.    stack_size = 0;  
32.    free(stack);  
33.    stack = NULL;  
34.}  
35.  
36./* push */  
37.void push(STACK_TYPE value)  
38.{  
39.    assert(!is_full());  
40.    top_element += 1;  
41.    stack[top_element] = value;  
42.}  
43.  
44./* pop */  
45.void pop(void)  
46.{  
47.    assert(!is_empty());  
48.    top_element -= 1;  
49.}  
50.  
51./* top */  
52.STACK_TYPE top(void)  
53.{  
54.    assert(!is_empty());  
55.    return stack[top_element];  
56.}  
57.  
58./* is_empty */  
59.int is_empty(void)  
60.{  
61.    assert(stack_size > 0);  
62.    return top_element == -1;  
63.}  
64.  
65./* is_full */  
66.int is_full(void)  
67.{  
68.    assert(stack_size > 0);  
69.    return top_element == stack_size - 1;  
70.}  

4.3 链式结构

  由于只有堆栈顶部元素才可以被访问,因此适用单链表可以很好实现链式堆栈,而且无长度限制。把一个元素压入堆栈是通过在链表头部添加一个元素实现。弹出一个元素是通过删除链表头部第一个元素实现。由于没有长度限制,故不需要create_stack函数,需要destroy_stack进行释放内存以避免内存泄漏。

  l_stack.c 源代码如下:

1./* 
2.** 单链表实现堆栈,没有长度限制 
3.*/  
4.#include"stack.h"  
5.#include<stdio.h>  
6.#include<malloc.h>  
7.#include<assert.h>  
8.  
9.#define FALSE 0  
10.  
11./* 
12.** 定义一个结构以存储堆栈元素。 
13.*/  
14.typedef struct STACK_NODE  
15.{  
16.    STACK_TYPE value;  
17.    struct STACK_NODE *next;  
18.} StackNode;  
19.  
20./* 指向堆栈中第一个节点的指针 */  
21.static StackNode *stack;  
22.  
23./* create_stack */  
24.void create_stack(size_t size)  
25.{}  
26.  
27./* destroy_stack */  
28.void destroy_stack(void)  
29.{  
30.    while(!is_empty())  
31.        pop();  /* 逐个弹出元素,逐个释放节点内存 */  
32.}  
33.  
34./* push */  
35.void push(STACK_TYPE value)  
36.{  
37.    StackNode *new_node;  
38.    new_node = (StackNode *)malloc(sizeof(StackNode));  
39.    if(new_node == NULL)  
40.        perror("malloc fail");  
41.    new_node->value = value;  
42.    new_node->next = stack;  /* 新元素插入链表头部 */  
43.    stack = new_node;       /* stack 重新指向链表头部 */  
44.}  
45.  
46./* pop */  
47.void pop(void)  
48.{  
49.    StackNode *first_node;  
50.      
51.    assert(!is_empty());  
52.    first_node = stack;  
53.    stack = first_node->next;  
54.    free(first_node);  
55.}  
56.  
57./* top */  
58.STACK_TYPE top(void)  
59.{  
60.    assert(!is_empty());  
61.    return stack->value;  
62.}  
63.  
64./* is_empty */  
65.int is_empty(void)  
66.{  
67.    return stack == NULL;  
68.}  
69.  
70./* is_full */  
71.int is_full(void)  
72.{  
73.    return FALSE;  
74.}  

  至此,嵌入式里堆栈原理及其纯C实现痞子衡便介绍完毕了,掌声在哪里~~~

欢迎订阅

文章会同时发布到我的 博客园主页CSDN主页微信公众号 平台上。

微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

痞子衡嵌入式:嵌入式里堆栈原理及其纯C实现