主頁 > 作業系統 > 痞子衡嵌入式:嵌入式里堆疊原理及其純C實作

痞子衡嵌入式:嵌入式里堆疊原理及其純C實作

2020-09-12 21:34:29 作業系統


  大家好,我是痞子衡,是正經搞技術的痞子,今天痞子衡給大家講的是嵌入式里堆疊原理及其純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主頁、微信公眾號 平臺上,

微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/17250.html

標籤:嵌入式

上一篇:痞子衡嵌入式:知名半導體MCU大廠軟體開發C代碼規范

下一篇:痞子衡嵌入式:ARM Cortex-M內核那些事(6)- 系統堆疊機制

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more