1. 程式人生 > >痞子衡嵌入式:嵌入式裡堆疊原理及其純C實現

痞子衡嵌入式:嵌入式裡堆疊原理及其純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等函式呼叫指令後硬體自動填充的堆疊。
  軟體堆疊:是編譯器為了處理一些引數傳遞而做的堆疊,會由編譯器自動產生和處理,可以通過相應的編譯選項對其進行編輯。
  簡單一點說,硬體堆疊主要做為地址堆疊用,而軟體堆疊主要會被分配成資料堆疊。或看其棧頂指標是否和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實現

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家講的是嵌入式裡堆疊原理及其純C實現。   今天給大家分享的這篇還是2016年之前痞子衡寫的技術文件,花了點時間重新編排了一下格式。棧這種結構在嵌入式裡其實是非常常用的,比如函式呼叫與返回就是典型的棧應用,雖然很多時候棧都是CPU系統在自動管理,我們

痞子嵌入式IVT的不同entry設定可能會造成i.MXRT1xxx系列啟動App後發生異常跑飛

----   大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是**IVT裡的不同entry設定可能會造成i.MXRT1xxx系列啟動App後發生異常跑飛問題的分析解決經驗**。   事情緣起恩智浦官方論壇上的一個疑問帖 [《RT1015 dev_cdc_vcom_freertos res

痞子嵌入式知名半導體MCU大廠軟體開發C程式碼規範

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家講的是飛思卡爾軟體開發C語言編碼規範。   2020鼠年春節是個漫長的假期,痞子衡在家百無聊賴,翻出了2016年10月1日(這個時間是痞子衡正式開始用markdown+github寫技術文章並發表到部落格園上的紀念日)之前寫的技術文件,不翻不知道,

痞子嵌入式JLink Script檔案基礎及其在IAR下呼叫方法

----   大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是**JLink Script檔案基礎及其在IAR下呼叫方法**。   JLink可以說是MCU開發者最熟悉的除錯工具了,相比於其他偵錯程式(比如DAPLink、ST-LINK、I-jet等),JLink除了效能強大之外,還勝在

痞子隨筆常用的數據傳輸差錯檢測技術(1)- 奇偶校驗(Parity Check)

編碼 和數 作用 除了 實現 1.5 怎麽辦 如果 錯誤   大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家講的是嵌入式數據傳輸裏的差錯檢測技術-奇偶校驗。   在嵌入式應用裏,除了最核心的數據處理外,我們還會經常和數據傳輸打交道。數據傳輸需要硬件傳輸接口的支持

區塊鏈100講HTTPS協議的原理及其與HTTP協議的區別

1 HTTPS協議是什麼 https協議比http協議多了一個s,字面意思上s=secure(安全)。它跟http協議一樣都是應用層協議,都是工作在TCP協議之上。 只不過https協議在傳輸過程中的資料都是經過了加密。本質上HTTPS協議就是在TCP協議之上又加了一層SS

MD5演算法原理介紹與C++實現

MD5演算法原理介紹與C++實現 原始碼傳送門:https://github.com/dick20/Web-Security/tree/master/MD5 一. 演算法原理概述 The MD5 message-digest algorithm is a wid

自己動手寫資料結構二叉樹BinaryTree類模板C++實現(功能較全)

#ifndef MYBINARYTREE_H #define MYBINARYTREE_H template <class T> class BinaryTree { protected: struct TNode { T val; TNode*

Qt佈局管理 停靠視窗QDockWidget類(程式碼實現

(注:以下解釋是我自己的翻譯,由於英文水平有限,望海涵) 詳細描述: QDockWidget類提供了一個窗體部件,其可以停靠在QMainWindow,或其本身作為一個在桌面上的頂級視窗(也就是父窗體

UDP flood 原理及原始碼 C實現

    UDP是一種不可靠的、無連線的資料報服務。源主機在傳送資料前不需要和目標主機建立連線。資料被冠以源、目標埠號等UDP報頭欄位後直接發往目的主機。這時,每個資料段的可靠性依靠上層協議來保證。在傳送資料較少、較小的情況下,UDP比TCP更加高效。 如圖示,UDP報文結

區塊鏈背後的資訊保安(3)橢圓曲線加解密及簽名演算法的技術原理及其Go語言實現

# 橢圓曲線加解密及簽名演算法的技術原理及其Go語言實現橢圓曲線加密演算法,即:Elliptic Curve Cryptography,簡稱ECC,是基於橢圓曲線數學理論實現的一種非對稱加密演算法。相比RSA,ECC優勢是可以使用更短的金鑰,來實現與RSA相當或更高的安全。據研究,160位ECC加密安全性相當

MD5加密演算法原理及其Go語言實現

MD5訊息摘要演演算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼雜湊函式,可以產生出一個128位元(16位元組)的雜湊值(hash value),用於確保資訊傳輸完整一致。 go 呼叫 md5 方法 新建 m

區塊鏈背後的資訊保安(1)AES加密演算法原理及其GO語言實現

# AES加密演算法原理及其GO語言實現AES是作為DES的替代標準出現的,全稱Advanced Encryption Standard,即:高階加密標準。AES加密演算法,經歷了公開的選拔,最終2000年,由比利時密碼學家Joan Daemen和Vincent Rijmen設計的Rijndael演算法被選中

痞子嵌入式飛思卡爾i.MX RTyyyy系列MCU硬體那些事(2.2)- 在序列NOR Flash XIP除錯原理

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是飛思卡爾i.MX RTyyyy系列EVK在序列NOR Flash除錯的原理。   本文是i.MXRT硬體那些事系列第二篇的續集,在第二篇首集中痞子衡給大家詳細介紹了EVK板載偵錯程式用法,有了偵錯程式在手,從此除錯不用愁。從除錯程式碼所在目

痞子嵌入式極精簡的Git命令教程(1)- 準備(init/config/.gitignore)

rec 精簡 gpo 課程 根據 信息 -- 文件中 嵌入式   今天是Git系列課程第一課,痞子衡給大家要講的是創建repo的準備工作。 1.建倉庫git init   第一步是創建一個空repo,這是一切操作的前提。 # 打開git bash命令行,切換到指定目錄下

痞子嵌入式極精簡的Git命令教程(2)- 連接(remote/clone)

我們 pair ssh key 技術 彈出 change 能夠 sha2 permanent   今天是Git系列課程第二課,上一課我們已經學會在本地創建一個空repo,痞子衡今天要講的是如何將本地倉庫與遠程建立聯系。 1.將本地倉庫掛上遠程git remote   本地

痞子嵌入式第一本Git命令教程(3)- 編輯(status/add/rm/mv)

this 通知 一次 ranch card use div 添加文件 app   今天是Git系列課程第三課,前兩課我們都是在做Git倉庫準備工作,今天痞子衡要講的是Git本地提交前的準備工作。   本地有了倉庫,我們便可以在倉庫所在目錄下做文件增刪改操作,這些操作默認都

痞子嵌入式第一本Git命令教程(5)- 提交(commit/format-patch/am)

今天 分布 控制系統 rom end stat 準備工作 多少 cond   今天是Git系列課程第五課,上一課我們做了Git本地提交前的準備工作,今天痞子衡要講的是Git本地提交操作。   當我們在倉庫工作區下完成了文件增刪改操作之後,並且使用git add將文件改動記

痞子嵌入式飛思卡爾i.MX RT系列微控制器啟動篇(2)- Boot配置(BOOT_CFG Pin/eFUSE)

TP 執行 不同 mcu 更新 oom als 配置 示例   大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是飛思卡爾i.MX RT系列MCU的Boot配置。   在上一篇文章 飛思卡爾i.MX RT系列微控制器啟動篇(1)- Boot簡介 裏痞子衡為大

痞子嵌入式飛思卡爾i.MX RT系列微控制器介紹篇(3)- 命名規則

rule 成員 oom 嵌入 信息 100% 規則 控制器 今天   大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是飛思卡爾i.MX RT系列MCU的命名規則。   打開任何一款i.MX RT系列芯片的Data Sheet均可找到如下命名規則表,以i.M