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

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

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

short c;

short g(short a) {
short b[] = {0x03}; short i = ; for(i = ; i<; i++)
{
b[] += b[i] + a;
} if(b[] < ) g(b[]); return b[];
} int main(void)
{
c=g();
}

使用 COSMIC 针对 s12z 的编译器 cxs12z 对代码进行编译,得到的汇编如下:

 .const:    section    .text
L3_b:
dc.w
ds.b
switch .text
_g:
psh d2
lea s,(-,s)
OFST: set
.dcall "30,2,_g"
lea x,(OFST-,s)
ld y,#L3_b
ld d2,#
L4:
mov.l (y+),(x+)
dbne d2,L4
clr.w (OFST-,s)
lea x,(OFST-,s)
ld d2,(OFST-,s)
L5:
ld d3,(x+)
add d3,(OFST+,s)
add d2,d3
inc.w (OFST-,s)
ld d4,(OFST-,s)
cmp d4,#
blt L5
st d2,(OFST-,s)
st x,(OFST-,s)
cmp d2,#
bge L31
jsr _g
.dcall "_g:_g"
ld d2,(OFST-,s)
L31:
lea s,(,s)
rts
_main:
.dcall "3,0,_main"
ld d2,#
jsr _g
.dcall "_main:_g"
st d2,_c
rts
xdef _main
xdef _g
switch .bss
_c:
ds.b
xdef _c
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 使用报告。

单片机 MCU 中 stack 使用的探讨的更多相关文章

  1. 单片机项目中使用新IC芯片的调试方法

    前两天,一位小伙伴咨询我一款新IC芯片怎么使用,借此机会我顺便把我日常工作中经常用到的一种调试方法介绍给小伙伴们,希望对对大家有所帮助.准备仓促,文中难免有技术性错误,欢迎大家给予指正,并给出好的建议 ...

  2. 单片机 MCU 固件打包脚本软件

    ​ 1 前言 开发完 MCU 软件后,通常都会生成 hex 文件或者 bin 文件,用来做固件烧录或者升级,如果用来做产品开发,就涉及到固件版本的问题,初学者通常采用固件文件重命名来区分版本. 如果需 ...

  3. STL中stack小结

    (1)为了运用stack,你必须包含头文件<stack>:#include<stack> (2)在头文件中stack定义如下: namespace std{ template ...

  4. 关于NOR_FLASH的大小在单片机程序中的应用

    在单片机开发中,NOR_FLASH常用的有4M和8M的大小: 4M的FLASH在程序中可以这样表示:Ptr < 0x220000 8M的FLASH在程序中可以这样表示:Ptr < 0x40 ...

  5. VB6单片机编程中的汉字处理

    在DOS时代,拥有一个华丽的汉字菜单几乎是每个高档中文应用程序必须的包装.中文Windows操作系统的出现使得高级开发平台实现全中文的提示和界面非常容易和方便.在一般的应用程序中已经很少需要去专门考虑 ...

  6. 如何给女朋友讲明白:Java 中 Stack(栈) 与 Heap(堆)

    背景 Java 中 Stack(栈) 与 Heap(堆) 是面试中被经常问到的一个话题. 有没有对 Java 中 Stack(栈) 与 Heap(堆) 烂熟于心的童鞋,请举手!!!(怎么没人举手-) ...

  7. 硬件知识整理part3--电阻在单片机系统中的应用

    邦有道,如矢:邦无道,如矢.  --孔子 电阻在电路中主要功能是限流和分压等等.在单片机系统中自然也是. 电阻作为限流应该是最常用的应用之一,对于单片机外围设计来说,电阻的应用非常重要,在很多时候,我 ...

  8. C++中stack

    参考:https://blog.csdn.net/u012655441/article/details/64920825 C++中stack的用法 转载:xueruifan的博客 C++ Stack( ...

  9. ADAS处理器集成功能安全单片机MCU

    ADAS处理器集成功能安全单片机MCU ADAS processors integrate functional safety MCU 拉斯维加斯-德州仪器公司引进了ADAS和网关处理器TDA4VM和 ...

随机推荐

  1. HBase API操作

    |的ascII最大ctrl+shift+t查找类  ctrl+p显示提示 HBase API操作 依赖的jar包 <dependencies> <dependency> < ...

  2. oracle数据库删除数据恢复

    select * from table_name as of timestamp trunc(sysdate)-10; 数字部分可以调整到最近时间内 复制表内容 insert into res_pro ...

  3. c++ <vector>学习

    https://www.cnblogs.com/mr-wid/archive/2013/01/22/2871105.html 具体参考上面博客,很详细,也是看他的.今天c++学习200%,项目开发0% ...

  4. Intellij IDEA实现SpringBoot项目多端口启动

    前言 有时候使用springboot项目时遇到这样一种情况,用一个项目需要复制很多遍进行测试,除了端口号不同以外,没有任何不同.这时我们强大的Intellij IDEA就能替我们实现. 实现方法 第一 ...

  5. Word技巧【转载】

    Word谁都会用,高手和菜鸟最明显的区别就是效率,有人可以轻车熟路一小时做完好几份Word,有人加班到深夜手酸脖子疼还没做好一份,不管用什么办公软件,效率都是第一追求.先看第一点:   ❶如何快速选中 ...

  6. 在UnrealEngine中用Custom节点实现径向模糊

    //input NotUse 为了开启SceneTextureLookup函数而连接的节点,但是不参与逻辑 //input UV 屏幕缓存的坐标坐标 //input Strength 力度 //inp ...

  7. Codeforces.1110E.Magic Stones(思路 差分)

    题目链接 听dalao说很nb,做做看(然而不小心知道题解了). \(Description\) 给定长为\(n\)的序列\(A_i\)和\(B_i\).你可以进行任意多次操作,每次操作任选一个\(i ...

  8. PHP的json_encode不兼容JSON_UNESCAPED_UNICODE的解决方案

    PHP5.4才支持JSON_UNESCAPED_UNICODE这个参数,此参数是让中文字符在json_encode的时候不用转义,减少数据传输量.但在PHP5.3中,就得自己写个函数来实现,以下就是解 ...

  9. [CF49E]Common ancestor

    [CF49E]Common ancestor 题目大意: 有两个由小写字母构成的字符串\(S\)和\(T(|S|,|T|\le50)\).另有\(n(n\le50)\)个形如\(a\to bc\)的信 ...

  10. 使用纯CSS制作展开合并立方体特效

    显示效果 源码 <html> <head> <meta http-equiv="Content-Type" content="text/ht ...