乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      STM32編程:是時候深入理解棧了<一>

       西北望msm66g9f 2020-05-16

      [導讀] 從這篇文章開始,將會不定期更新關(guān)于嵌入式C語言編程相關(guān)的個人認為比較重要的知識點,或者踩過的坑。

      為什么要深入理解棧?做C語言開發(fā)如果棧設置不合理或者使用不對,棧就會溢出,溢出就會遇到無法預測亂飛現(xiàn)象。所以對棧的深入理解是非常重要的。

      注:動畫如果看不清楚可以電腦看更清晰

      啥是棧

      先來看一段動畫:

      沒有比這個更直觀的啦,棧是一種受限的數(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++ DevelopmentGuide,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:\n');
          x = factorial(n);
          /*打印棧指針變化情況*/
          for(int i = 0;i<spIndex;i++)
              printf('MSP->%08X\n',spSatte[i]);
          
          /*打印階乘結(jié)果*/
          printf('factorial(%d)=%f\n',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。

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

      對這個動圖解讀一下:

      • 進入復位是,SP_main為0x200007D8,指向棧底,為空棧。那么這是怎么實現(xiàn)的呢?
      __vector_table                ;向量表
      DCD sfe(CSTACK) ;這條命令會將程序的CSTACK起始地址裝載給SP_main
      DCD Reset_Handler ; Reset Handler復位向量
      • 前面說0x200007D8并沒有用到,怎么證明呢,在函數(shù)進入mian時,第一次壓棧的情況如下:

      • 可見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)
      • 棧在嵌入式編程領(lǐng)域隨處可見,比如C棧,中斷棧、異常棧、任務棧等等,但其基本工作原理都一樣。支持兩種基本數(shù)據(jù)操作:壓棧、出棧。
      • 棧溢出程序的結(jié)果無法預期,所以合理的設置棧區(qū)大小是個永恒的話題,過大則浪費內(nèi)存,過小則程序會飛。
      • 嵌入式編程遞歸函數(shù)要慎用,個人建議不用。比如IEC61508 功能安全標準中強行規(guī)定不可使用遞歸函數(shù)。
      • STM32中__get_MSP可以得到當前棧指針的值,據(jù)此可以做一定程度的棧溢出保護措施。防止程序跑飛。
      • 通過上面遞歸調(diào)用測試,還可以得到一個啟示,嵌入式編程函數(shù)嵌套的層級不宜過深,過深則需要相對較大的棧開銷。
      • .......
      點擊留言/查看留言

      END

      果喜歡右下點個在看,也會讓我倍感鼓舞

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多