單片機 MCU 中 stack 使用的探討

stack 的使用,是單片機開發中影響最大,可是最少被討論的問題。而說起這個問題的地方,都是對這個問題含糊其辭。數組

今天花了點時間,使用最笨的辦法,直接閱讀彙編代碼,來對這個問題就行探究,這裏作一下記錄。函數

下面是本次實驗使用的代碼,代碼自己沒有意義,僅做探討 stack 相關問題使用:spa

short c;

short g(short a) {
    short b[10] = {0x03};

    short i = 0;
    
    for(i = 0; i<10; i++)
    {
        b[1] += b[i] + a;
    }

    if(b[1] < 200)    g(b[1]);

    return b[1];
}

int main(void)
{
    c=g(1);
}

使用 COSMIC 針對 s12z 的編譯器 cxs12z 對代碼進行編譯,獲得的彙編以下:翻譯

 1 .const:    section    .text
 2 L3_b:
 3     dc.w    3
 4     ds.b    18
 5     switch    .text
 6 _g:
 7     psh    d2
 8     lea    s,(-25,s)
 9 OFST:    set    25
10     .dcall    "30,2,_g"
11     lea    x,(OFST-25,s)
12     ld    y,#L3_b
13     ld    d2,#5
14 L4:
15     mov.l    (y+),(x+)
16     dbne    d2,L4
17     clr.w    (OFST-2,s)
18     lea    x,(OFST-25,s)
19     ld    d2,(OFST-23,s)
20 L5:
21     ld    d3,(x+)
22     add    d3,(OFST+0,s)
23     add    d2,d3
24     inc.w    (OFST-2,s)
25     ld    d4,(OFST-2,s)
26     cmp    d4,#10
27     blt    L5
28     st    d2,(OFST-23,s)
29     st    x,(OFST-5,s)
30     cmp    d2,#200
31     bge    L31
32     jsr    _g
33     .dcall    "_g:_g"
34     ld    d2,(OFST-23,s)
35 L31:
36     lea    s,(27,s)
37     rts
38 _main:
39     .dcall    "3,0,_main"
40     ld    d2,#1
41     jsr    _g
42     .dcall    "_main:_g"
43     st    d2,_c
44     rts
45     xdef    _main
46     xdef    _g
47     switch    .bss
48 _c:
49     ds.b    2
50     xdef    _c
51     end

閱讀彙編代碼:指針

1. 第 1-5 行,存儲數組 b 的初始化值到 const 區。b[0]爲3,b[1]-b[9] 爲 0。code

2. 第 6 行,函數 g 的標籤。blog

3. 第 41 行調用了函數 g,並使用寄存器 d2 傳遞 short 型參數給函數 g。根據 Compiler 的文檔,調用函數時若是須要傳遞參數,優先使用 cpu 的 register,符合下面要求的第一個參數將被放入相應寄存器,不符合的話,會合其它參數一塊兒,被放入 stack。參數會按從右到左的數序壓入 stack。這裏顯然 short 是能夠放入 d2 的,並且只有一個參數,沒有用到 stack。開發

char arguments are passed in d0 and d1, short, int and short _Fract arguments are passed in d2, d3, d4 and d5, long, long _Fract and float arguments are passed in d6 and d7, double and long long arguments are passed in d6:d7,

這裏須要注意,jsr 調用,自己是會將當前 PC 壓入 stack 的(3 個 byte),因此,只是傳遞參數沒有使用 stack。文檔

4. 第 7 行,由於後面的運算可能用到 register d2,因此,先將 d2 壓入 stack。編譯器

5. 第 8-9 行,s12z 的 stack 操做方式相關,直接對 s 寄存器操做,在 stacks 上爲當前函數預留 25 個 bytes 使用。

6. 第 11 行,將 (OFST-25, s )這個地址賦給 x 寄存器,事實上,也就是 b[0] 的地址。

7. 第 12-16 行,對數組 b 進行初始化。每次從 ROM 拷貝 4 個 bytes 到 stack 上,共拷貝了 5 次。

8. 第 17 行,對變量 i 進行了初始化,變量 i 地址爲 (OFST-2, s).

9. 第 18 行,再次讀取 b[0] 地址到 x 寄存器。

10. 第 19 行,讀取 b[1] 內容到 d2 寄存器。c 代碼中第二個循環有個累加操做,累加的值會被暫存在 d2 中。

11. 第 21 行,第 25-27 行的判斷跳轉,這中間的代碼完成了 for 循環。這裏是加載 b[i] 到寄存器 d3;x 寄存器從(OFST-25,s)開始自加,歷遍 b[i]。

12. 第 22.行,b[i] + a

13. 第 23 行,d2 實施上存儲的是 b[1] 的值。b[1] += b[i] + a

14. 第 24 行,i++

15. 第 28 行,for 循環已經結束。再保存 d2 到 b[1] 在 stack 上的存儲空間;和第 19 行相反的操做。

16. 第 29 行,將地址 x (3 byte,24 位)保存到 (OFST-5, s) 開始的 stack 空間。

17. 第 30-32 行,if(b[1] < 200) g(b[1])。這裏有兩個分支:

  • a. 當 b[1] 小於 200 時,再次用 jsr 調用函數 g。現實使用 3bytes 的 stack 空間保存了當前的 PC 值,而後進入 g 以後,從新分配了 27 bytes 給下一次調用使用!!!仍然使用 d2(b[1])傳遞參數給函數 g。
  • b. 當 b[1] 大於等於 200 時,跳到分支 L31 執行。第 35-37 行,釋放掉本次調用佔用的 stack 空間,共 27 bytes。第 37 行的 rts 返回的位置,是上一次 jsr _g 的下一行;以前申請的 stack 空間,在迭代達到最深後,隨着一個個 rts,前後被釋放。

18. 第 34 行,這裏,函數迭代調用結束;即,當 jsr _g 的 rts 達到值後,會繼續從這裏執行。因此,最後一次的 b[1] 被從新賦值給 d2,經過 d2 將返回值傳給 mian 函數。在第 43 行,返回值被從 d2 傳輸到變量 c 的 RAM 空間。

19. 第 40 行,將當即數 1 存入 d2 寄存器。

20. 第 41 行,調用函數 g。

21. 第 42 行,將返回值賦值賦值給 c。

22. 第 45-46 行,聲明全局變量,供 link 使用。

23. 第 49-50 行,聲明全局變量 c,大小爲 2 bytes。

 

另外,仍是用 arm-noneabi-gcc 編譯了上面 C 代碼,產生的彙編也是大同小異。可見,你們翻譯 C 語言和翻譯爲彙編時,大的討論是差很少的。

 

上面的代碼涉及了 stack 調用狀況的:

1. 函數跳轉時,保存 PC 指針 3 bytes;

2. 函數內部變量,即局部變量被放在 stack 上,在這裏是。

 

上面的代碼沒有涵蓋的使用:

1. 使用 stack 傳遞參數;

2. 使用 stack 傳遞返回值。

3. 中斷跳轉和中斷返回自動進行 stack 操做的情形。

 

此外,編譯完成後,該編譯器會在 map 文件中彙總分析 stack 的使用狀況;在使用 arm-noneabi-gcc 時,產生的每一個彙編函數都有進行 stack 使用報告。

相關文章
相關標籤/搜索