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])。這裏有兩個分支:
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 使用報告。