你們好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給你們講的是嵌入式裏堆棧原理及其純C實現。node
今天給你們分享的這篇仍是2016年以前痞子衡寫的技術文檔,花了點時間從新編排了一下格式。棧這種結構在嵌入式裏實際上是很是經常使用的,好比函數調用與返回就是典型的棧應用,雖然不少時候棧都是CPU系統在自動管理,咱們只須要在連接文件裏分配棧大小以及棧存放位置,但稍微瞭解一下棧的原理會更加利於咱們去理解嵌入式代碼執行機制,以及幫助咱們進一步去調試。程序員
堆HEAP與棧STACK是兩個不一樣概念,其本質上都是一種數據結構。
棧是一種按數據項排列的數據結構,只能在一端(棧頂top)對數據項進行插入和刪除,其符合後進先出(Last-In / First-Out)原則。棧(os)通常是由編譯器自動分配釋放,其使用的是一級緩存。
堆也是一種分配方式相似於鏈表的數據結構,其能夠在任意位置對數據項進行操做。堆(os)通常由程序員手動分配釋放,其使用的是二級緩存。
在嵌入式世界裏,堆棧通常指的僅是棧。數組
在MCU中,棧這種結構通常被cpu和os所使用。
在cpu裸機中使用狀況分兩種:1、主動進行函數調用時,STACK用以暫存下一條指令地址、函數參數、函數中定義的局部變量;2、硬中斷來臨時,暫存當前執行的現場數據(下一條指令地址、各類緩存數據),中斷結束後,用以恢復。
在os中使用時,硬棧的使用同cpu裸機;但os通常會爲每一個任務額外分配一個軟棧,在任務調度時,可用軟中斷打斷當前正在執行的任務,棧則用以保存各自任務以恢復。緩存
硬件堆棧:是經過寄存器SP做爲索引指針的地址,是調用了BL等函數調用指令後硬件自動填充的堆棧。
軟件堆棧:是編譯器爲了處理一些參數傳遞而作的堆棧,會由編譯器自動產生和處理,能夠經過相應的編譯選項對其進行編輯。
簡單一點說,硬件堆棧主要作爲地址堆棧用,而軟件堆棧主要會被分配成數據堆棧。或看其棧頂指針是否和CPU具備特殊的關聯,有關聯者(如SP)「硬」,而無關聯者「軟」。微信
基本的抽象數據類型(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);
在靜態數組堆棧中,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 源代碼以下:.net
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.}
頭文件仍是用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.}
因爲只有堆棧頂部元素才能夠被訪問,所以適用單鏈表能夠很好實現鏈式堆棧,並且無長度限制。把一個元素壓入堆棧是經過在鏈表頭部添加一個元素實現。彈出一個元素是經過刪除鏈表頭部第一個元素實現。因爲沒有長度限制,故不須要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主頁、微信公衆號 平臺上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就能夠在手機上第一時間看了哦。