• 单片机 MCU 中 stack 使用的探讨


    stack 的使用,是单片机开发中影响最大,但是最少被讨论的问题。而提及这个问题的地方,都是对这个问题含糊其辞。

    今天花了点时间,使用最笨的办法,直接阅读汇编代码,来对这个问题就行探究,这里做一下记录。

    下面是本次实验使用的代码,代码本身没有意义,仅作探讨 stack 相关问题使用:

    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。

    2. 第 6 行,函数 g 的标签。

    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 使用报告。

  • 相关阅读:
    为什么你不是优秀的人?是这个原因么?
    我们应选择怎样的IT公司
    如何获得加薪
    隐藏为了适时出现
    如何通过一个问题,完成最成功的技术面试
    阿里负责人揭秘面试潜规则
    应聘互联网公司的简历应该是怎么样的?
    linq 图解
    Lambda表达式的前世今生
    Lambda应用设计模式
  • 原文地址:https://www.cnblogs.com/pied/p/9492643.html
Copyright © 2020-2023  润新知