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. Python Enum 枚举 用法汇总

    Python Enum 枚举 用法汇总 import os import sys if sys.version_info.major + sys.version_info.minor * 0.1 &l ...

  2. jQuery获得页面绝对和相对的位置

    获得某一元素绝对x,y位置,可以用offset方法 var X = $('#DivID').offset().top; var y=$("#divid").offset().lef ...

  3. Python 爬虫利器 Selenium 介绍

    Python 爬虫利器 Selenium 介绍 转 https://mp.weixin.qq.com/s/YJGjZkUejEos_yJ1ukp5kw 前面几节,我们学习了用 requests 构造页 ...

  4. 从函数式编程到Ramda函数库(一)

    函数式编程是种编程方式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值).和指令式编程相比, ...

  5. 大数据小白系列——MR(1)

    一部编程发展史就是一部程序员偷懒史,MapReduce(下称MR)同样是程序员们用来偷懒的工具. 来了一份大数据,我们写了一个程序准备分析它,需要怎么做? 老式的处理方法不行,数据量太大时,所需的时间 ...

  6. 平衡二叉树的java实现

    转载请注明出处! 一.概念 平衡二叉树是一种特殊的二叉搜索树,关于二叉搜索树,请查看上一篇博客二叉搜索树的java实现,那它有什么特别的地方呢,了解二叉搜索树的基本都清楚,在按顺序向插入二叉搜索树中插 ...

  7. P=(1+1/(1-L))/2

    1 y=1/x 2 y=1/(1-x) 3 4

  8. Android动画曲线库AndroidEasingFunctions

    Android动画曲线库AndroidEasingFunctions AndroidEasingFunction是基于Easing Function(缓动函数)的Android动画曲线库.它提供了九大 ...

  9. java 实验 三 (2)(3)

    (2)编写一个Java程序,声明一个数组,各元素如:{12, 23, 456, 222, 768, 93245}计算出所有元素的和以及所有元素的平均值:SumArray.java(3)编写一个Java ...

  10. vector.erase用法注意事项

    转自->这里 vector::erase():从指定容器删除指定位置的元素或某段范围内的元素 vector::erase()方法有两种重载形式 如下: iterator erase(iterat ...