91欧美超碰AV自拍|国产成年人性爱视频免费看|亚洲 日韩 欧美一厂二区入|人人看人人爽人人操aV|丝袜美腿视频一区二区在线看|人人操人人爽人人爱|婷婷五月天超碰|97色色欧美亚州A√|另类A√无码精品一级av|欧美特级日韩特级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

棧是一種LIFO后入先出的什么數(shù)據(jù)結(jié)構(gòu)?

lhl545545 ? 來源:玩轉(zhuǎn)單片機 ? 作者:玩轉(zhuǎn)單片機 ? 2020-06-09 09:26 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

沒有比這個更直觀的啦,棧是一種受限的數(shù)據(jù)結(jié)構(gòu)模型,其數(shù)據(jù)總是只能在頂部追加,利用一個指針進行索引,頂端叫棧頂,相對的一端底部稱為棧底。棧是一種LIFO后入先出的數(shù)據(jù)結(jié)構(gòu)。

棧就兩種操作:

PUSH,壓棧,向棧內(nèi)加入數(shù)據(jù),

POP,出棧

再進一步探討:

首先將棧與堆分清,從看到這篇文章開始,我建議你不要把堆和棧連在一起叫,棧是棧,堆是堆,這是兩回事,別混為一談?。ǘ驯疚牟簧钊胗懻摚?/p>

從C/C++編程語言的角度來看:

相同點:都是一片內(nèi)存區(qū),在鏈接時指定棧區(qū)/堆區(qū)的位置以及大小。

不同點:

棧:由編譯器分配,存放函數(shù)的參數(shù)值,局部變量,寄存器組(不同的單片機/處理器各有不同)、函數(shù)調(diào)用參數(shù)傳遞、中斷異常產(chǎn)生時須保存處理器狀態(tài)的寄存器值等

堆:由程序員分配釋放,對于C而言,malloc、realloc/free進行分配/釋放,對C++而言,由new/delete分配/釋放。

為啥用

棧這個數(shù)據(jù)模型的應用價值是什么呢?先來看一下單片機內(nèi)部的可能有哪些棧應用?以STM32為例,參考IAR C/C++ Development Guide,P207

處理器模式建議段名描述

SupervisorSVC_STACK操作系統(tǒng)

IRQIRQ_STACK通用(IRQ)中斷處理程序的堆棧。

FIQFIQ_STACK用于高速(FIQ)中斷處理程序的堆棧。

UndefinedUND_STACK堆棧用于未定義的指令中斷。支持硬件協(xié)處理器和指令集擴展的軟件仿真。

AbortABT_STACK用于指令獲取和數(shù)據(jù)訪問存儲器中止中斷處理程序的堆棧。

如果使用RTOS還有任務棧,如果是Linux,其內(nèi)核線程同樣也需要棧的支持,等等這一切的一切棧,其本質(zhì)上都是利用了棧數(shù)據(jù)模型的LIFO后入先出的特性,一個典型應用場景就是比如做一件事情做到一半而要轉(zhuǎn)而去做另外一件事,對于芯片編程而言,就需要將當前的工作做個暫存,等另外一件事情做完了,再接著回來繼續(xù)做。那么怎么做到呢,以一個中斷處理為例,要記住當前的工作態(tài)有哪些信息需要暫存呢?PC指針,局部變量等就被壓入棧,再將中斷服務程序地址導入PC指針,進而去執(zhí)行中斷服務程序,待中斷處理完畢,在將棧里的內(nèi)容按照后入先出彈出到對應的寄存器就恢復了原程序的現(xiàn)場,進而繼續(xù)執(zhí)行。

怎么用

棧在哪里定義大小,定多大合適?這可能很多剛接觸單片機開發(fā)的同學不是太清楚,下面就將比較常見的IAR開發(fā)環(huán)境為例如何定義棧定義棧大小的地方說明一下,這里以IAR8.4.1為例,有兩種方式可以進行棧大小設置。

IDE設置

為了更加清楚明了,制作了一個GIF操作展示視頻,在stack/heap中就可以設置了,其中stack用于設置棧區(qū)大小,heap用于設置堆大小。

這個demo中設置了其棧的大小為0x200,堆的大小為0x400,全編譯后,檢查map文件就印證了棧/堆的大小如預期所修改。

鏈接配置文件

其實對于比較熟悉的開發(fā)人員,上一種方式并非推薦用法。用鏈接配置文件將具有更好的靈活性,比如可以指定一個段的對齊方式,存儲位置,某個符號的存儲位置等等。這里同樣為了直觀也做了一個GIF動畫,介紹如何通過鏈接文件進行棧/堆的大小配置。

其最終的效果也一樣如預期將棧區(qū)的大小設置好了。

棧溢出

這里為了比較容易的展示棧溢出的問題,在main函數(shù)利用遞歸方法計算階乘,代碼如下:

#include 《stdio.h》

#include “main.h”

static uint32_t spSatte[200];

static uint32_t spIndex = 0;

/*為什么要用浮點數(shù),因為數(shù)據(jù)非常大整型很快就會溢出*/

float factorial(uint32_t n)

uint32_t sp = __get_MSP

/*記錄棧指針的變化情況*/

spSatte[spIndex++] = sp;

if(n==0 || n==1)

return 1;

else

return (float)n*factorial(n-1);

int main(void)

float x = 0;

uint32_t n = 20;

printf(“stack test:

x = factorial(n);

/*打印棧指針變化情況*/

for(int i = 0;i《spIndex;i++)

printf(“MSP-》%08X

spSatte[i]);

/*打印階乘結(jié)果*/

printf(“factorial(%d)=%f

n,x);

while (1)

為方便觀察,將stm32f407xx_flash.icf 將棧改為256字節(jié)

/*stm32f407xx_flash.icf 將棧改為256字節(jié)*/

define symbol __ICFEDIT_size_cstack__ = 0x200;

define symbol __ICFEDIT_size_heap__ = 0x200;

全編譯后通過map文件來看下棧/堆的分配情況:

“P2”, part 3 of 3: 0x400

CSTACK 0x2000‘05d8 0x200 《Block》

CSTACK uninit 0x2000’05d8 0x200 《Block tail》

HEAP 0x2000‘07d8 0x200 《Block》

HEAP uninit 0x2000’07d8 0x200 《Block tail》

- 0x2000‘09d8 0x400

直觀些,翻譯成下圖,CSTACK段分配在0x2000 0280-0x2000 0480,堆分配在0x2000 0480-0x2000 0680。

棧是一種LIFO后入先出的什么數(shù)據(jù)結(jié)構(gòu)?

圖為什么沒有將0x2000 07D8畫在棧區(qū)呢?通過調(diào)試發(fā)現(xiàn),這個字空間沒有用做棧的實際存儲。將工程設置成simulation模式,debug進入main.o勾選掉,我們來計算20的階乘,來具體看一下:

棧是一種LIFO后入先出的什么數(shù)據(jù)結(jié)構(gòu)?

對這個動圖解讀一下:

進入復位是,SP_main為0x200007D8,指向棧底,為空棧。那么這是怎么實現(xiàn)的呢?

__vector_table ;向量表

DCD sfe(CSTACK) ;這條命令會將程序的CSTACK起始地址裝載給SP_main

DCD Reset_Handler ; Reset Handler復位向量

前面說0x200007D8并沒有用到,怎么證明呢,在函數(shù)進入mian時,第一次壓棧的情況如下:

棧是一種LIFO后入先出的什么數(shù)據(jù)結(jié)構(gòu)?

可見STM32棧的增長方向是向下增長的,也即頂在小地址端一側(cè)

棧存儲元素是四字節(jié)對齊的,因為STM32的字長是字節(jié),如果深入想想,如果不是司字節(jié)對齊會怎么樣?留給感興趣的思考一下。

0x200007D8--0x200007DB 這個字存儲單元并不是棧的有效存儲空間。

棧的變化情況:

stack test:

MSP-》200007A8

MSP-》20000790

MSP-》20000778

MSP-》20000760

MSP-》20000748

MSP-》20000730

MSP-》20000718

MSP-》20000700

MSP-》200006E8

MSP-》200006D0

MSP-》200006B8

MSP-》200006A0

MSP-》20000688

MSP-》20000670

MSP-》20000658

MSP-》20000640

MSP-》20000628

MSP-》20000610

MSP-》200005F8

MSP-》200005E0

factorial(20)=2432902023163674771.785700 /*結(jié)算結(jié)果與用計算器一致*/

每調(diào)用一次階乘函數(shù),棧就壓入4個字,由上面還可以看到第20次進入時,棧指針為0x200005E0,如果再壓入4個字棧指針會變成0x200005C8,是這樣嗎,結(jié)果還對嗎?將n改為21編譯運行,來看一看:

看到了吧,驚喜來了,棧溢出了,程序已經(jīng)不聽話了,完全不知道在干嘛了。所以棧溢出的后果是極端危險的,完全無法預期,程序會帶來什么后果。

總結(jié)一下

棧是一種LIFO后入先出的數(shù)據(jù)結(jié)構(gòu)模型,是C/C++程序運行時基礎,沒這個棧,C/C++玩不轉(zhuǎn)

棧在嵌入式編程領域隨處可見,比如C棧,中斷棧、異常棧、任務棧等等,但其基本工作原理都一樣。支持兩種基本數(shù)據(jù)操作:壓棧、出棧。

棧溢出程序的結(jié)果無法預期,所以合理的設置棧區(qū)大小是個永恒的話題,過大則浪費內(nèi)存,過小則程序會飛。

嵌入式編程遞歸函數(shù)要慎用,個人建議不用。比如IEC61508 功能安全標準中強行規(guī)定不可使用遞歸函數(shù)。

STM32中__get_MSP可以得到當前棧指針的值,據(jù)此可以做一定程度的棧溢出保護措施。防止程序跑飛。

通過上面遞歸調(diào)用測試,還可以得到一個啟示,嵌入式編程函數(shù)嵌套的層級不宜過深,過深則需要相對較大的棧開銷。
責任編輯:pj

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    FIFO存儲器的種類、IP配置及應用

    FIRST IN FIRST OUT (先先出)。顧名思義,F(xiàn)IFO是數(shù)據(jù)具有先進先出的存儲器。
    的頭像 發(fā)表于 01-13 15:15 ?495次閱讀
    FIFO存儲器的種類、IP配置及應用

    RDMA設計12:融合以太網(wǎng)協(xié)議設計1

    數(shù)據(jù)存取過程中的尋址速度。融合以太網(wǎng)協(xié)議按照功能劃分為連接管理模塊、隊列管理模塊、RoCE v2 發(fā)送模塊、RoCE v2 接收模塊、ICRC 生成校驗模塊及擁塞管理模塊六個模塊以及個 DMA
    發(fā)表于 12-25 11:39

    單片機堆棧解析

    什么是單片機堆棧? 在片內(nèi)RAM中,常常要指定個專門的區(qū)域來存放某些特別的數(shù)據(jù),它遵循順序存取和后進先出(LIFO/FILO)的原則,這個RAM區(qū)叫堆棧。 它的作用 子程序調(diào)用和中斷
    發(fā)表于 12-09 06:52

    typedef結(jié)構(gòu)體使用

    雖然結(jié)構(gòu)體的出現(xiàn)能夠讓我們有個更科學的數(shù)據(jù)結(jié)構(gòu)來管理數(shù)據(jù),但是每次使用結(jié)構(gòu)體都需要struct...,未免顯得有些冗長和麻煩。有了type
    發(fā)表于 12-08 07:04

    堆和的區(qū)別

    個由C/C 編譯的程序占用的內(nèi)存分為以下幾個部分: 區(qū)(stack):由編譯器自動分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的。 堆區(qū)(heap
    的頭像 發(fā)表于 11-27 18:13 ?1157次閱讀

    C語言程序的結(jié)構(gòu)

    )(void); //處理程序   uInt8 ms_count; //時間片大小   } _op_;   數(shù)據(jù)結(jié)構(gòu)定義好之后,接著就是實現(xiàn)代碼,包括三部分,即初始化數(shù)據(jù)、時間片的刷新與時間到執(zhí)行
    發(fā)表于 11-26 08:12

    優(yōu)先級隊列介紹

    隊列(Queue)的知識點:「概念」:隊列是一種先進先出(FIFO)的數(shù)據(jù)結(jié)構(gòu),類似于排隊的概念?!富静僮鳌梗篹nqueue(item): 將元素添加到隊列的末尾。dequeue(): 從隊列
    發(fā)表于 11-26 07:56

    堆棧指針SP介紹

    SP 堆棧指針:8位寄存器,用來指示堆棧的位置,可由軟件修改。 堆棧的介紹堆棧是一種按“先進出”規(guī)律操作的存儲結(jié)構(gòu)。不同類型的處理器其堆棧的設計各不相同: SP寄存器作為堆棧指針。這種結(jié)構(gòu)
    發(fā)表于 11-17 06:07

    UART接口數(shù)據(jù)線接收和發(fā)送數(shù)據(jù)

    FIFO,F(xiàn)IFO會按照先先出的順序?qū)?b class='flag-5'>數(shù)據(jù)依次彈出,每彈出個表項的字節(jié)數(shù)據(jù),則將此字節(jié)數(shù)據(jù)
    發(fā)表于 10-29 07:37

    獲取商品券價接口設計與實現(xiàn)

    "], // 可用優(yōu)惠券 "platform": "app" // 調(diào)用平臺} ? 響應數(shù)據(jù)結(jié)構(gòu) : ? { "original_price": 299.00, // 原價 "discounted_price
    的頭像 發(fā)表于 10-20 15:58 ?648次閱讀
    獲取商品券<b class='flag-5'>后</b>價接口設計與實現(xiàn)

    使用USB傳輸數(shù)據(jù)段時間能正常運行但是不會再上發(fā)數(shù)據(jù),為什么?

    ;TxState直為1,無法置為0再次進行發(fā)送 通過強制復位USB可以短暫解決問題,但是過了段時間(大概30~60分鐘)就有兩情況出現(xiàn),一種
    發(fā)表于 09-18 07:59

    分享個嵌入式學習階段規(guī)劃

    給大家分享個嵌入式學習階段規(guī)劃: ()基礎筑牢階段(約 23 天) 核心目標:打牢 C 語言、數(shù)據(jù)結(jié)構(gòu)、電路基礎C 語言開發(fā):學變量 / 指針 / 結(jié)構(gòu)體等核心語法,用 Dev-
    發(fā)表于 09-12 15:11

    【HZ-T536開發(fā)板免費體驗】6、使用protoc-gen-gorm生成標準化的數(shù)據(jù)結(jié)構(gòu)

    在設計espnow協(xié)議的時候,考慮到我需要在esp32,Linux設備,web上使用相同的數(shù)據(jù)結(jié)構(gòu),那就需要考慮下,是否使用個通用的跨平臺序列化數(shù)據(jù)結(jié)構(gòu)。這時候我想起了protob
    發(fā)表于 08-26 00:32

    使用USB傳輸數(shù)據(jù)段時間能正常運行但是不會再上發(fā)數(shù)據(jù),為什么?怎么解決?

    ;gt;TxState直為1,無法置為0再次進行發(fā)送 通過強制復位USB可以短暫解決問題,但是過了段時間(大概30~60分鐘)就有兩情況出現(xiàn),
    發(fā)表于 06-10 08:22

    程序設計與數(shù)據(jù)結(jié)構(gòu)

    《程序設計與數(shù)據(jù)結(jié)構(gòu)》重點闡述了三大方向內(nèi)容: 1. C語言學習中的痛點:針對當前工程師在C語言學習中的痛點,如指針函數(shù)與函數(shù)指針,如何靈活應用結(jié)構(gòu)體等。從變量的三要素(變量的類型,變量的值和變量
    發(fā)表于 05-13 16:45