痞子衡嵌入式:嵌入式里堆棧原理及其純C實現
大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家講的是嵌入式里堆棧原理及其純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等函數調用指令后硬件自動填充的堆棧。
軟件堆棧:是編譯器為了處理一些參數傳遞而做的堆棧,會由編譯器自動產生和處理,可以通過相應的編譯選項對其進行編輯。
簡單一點說,硬件堆棧主要做為地址堆棧用,而軟件堆棧主要會被分配成數據堆棧?;蚩雌錀m斨羔樖欠窈虲PU具有特殊的關聯,有關聯者(如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.** 創建堆棧,參數指定堆??梢员4娑嗌賯€元素。
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主頁、微信公眾號 平臺上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。

最后歡迎關注痞子衡個人微信公眾號【痞子衡嵌入式】,一個專注嵌入式技術的公眾號,跟著痞子衡一起玩轉嵌入式。
衡杰(痞子衡),目前就職于恩智浦(NXP)半導體MCU系統應用部門,擔任高級嵌入式系統應用工程師。
專欄內所有文章的轉載請注明出處:http://www.rzrgm.cn/henjay724/
與痞子衡進一步交流或咨詢業務合作請發郵件至 hengjie1989@foxmail.com
可以關注痞子衡的Github主頁 https://github.com/JayHeng,有很多好玩的嵌入式項目。
關于專欄文章有任何疑問請直接在博客下面留言,痞子衡會及時回復免費(劃重點)答疑。
痞子衡郵箱已被私信擠爆,技術問題不推薦私信,堅持私信請先掃碼付款(5元起步)再發。
浙公網安備 33010602011771號