单片机 MCU 中 stack 使用的探讨
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 使用的探讨的更多相关文章
- 单片机项目中使用新IC芯片的调试方法
前两天,一位小伙伴咨询我一款新IC芯片怎么使用,借此机会我顺便把我日常工作中经常用到的一种调试方法介绍给小伙伴们,希望对对大家有所帮助.准备仓促,文中难免有技术性错误,欢迎大家给予指正,并给出好的建议 ...
- 单片机 MCU 固件打包脚本软件
1 前言 开发完 MCU 软件后,通常都会生成 hex 文件或者 bin 文件,用来做固件烧录或者升级,如果用来做产品开发,就涉及到固件版本的问题,初学者通常采用固件文件重命名来区分版本. 如果需 ...
- STL中stack小结
(1)为了运用stack,你必须包含头文件<stack>:#include<stack> (2)在头文件中stack定义如下: namespace std{ template ...
- 关于NOR_FLASH的大小在单片机程序中的应用
在单片机开发中,NOR_FLASH常用的有4M和8M的大小: 4M的FLASH在程序中可以这样表示:Ptr < 0x220000 8M的FLASH在程序中可以这样表示:Ptr < 0x40 ...
- VB6单片机编程中的汉字处理
在DOS时代,拥有一个华丽的汉字菜单几乎是每个高档中文应用程序必须的包装.中文Windows操作系统的出现使得高级开发平台实现全中文的提示和界面非常容易和方便.在一般的应用程序中已经很少需要去专门考虑 ...
- 如何给女朋友讲明白:Java 中 Stack(栈) 与 Heap(堆)
背景 Java 中 Stack(栈) 与 Heap(堆) 是面试中被经常问到的一个话题. 有没有对 Java 中 Stack(栈) 与 Heap(堆) 烂熟于心的童鞋,请举手!!!(怎么没人举手-) ...
- 硬件知识整理part3--电阻在单片机系统中的应用
邦有道,如矢:邦无道,如矢. --孔子 电阻在电路中主要功能是限流和分压等等.在单片机系统中自然也是. 电阻作为限流应该是最常用的应用之一,对于单片机外围设计来说,电阻的应用非常重要,在很多时候,我 ...
- C++中stack
参考:https://blog.csdn.net/u012655441/article/details/64920825 C++中stack的用法 转载:xueruifan的博客 C++ Stack( ...
- ADAS处理器集成功能安全单片机MCU
ADAS处理器集成功能安全单片机MCU ADAS processors integrate functional safety MCU 拉斯维加斯-德州仪器公司引进了ADAS和网关处理器TDA4VM和 ...
随机推荐
- Count the string kmp
问题描述众所周知,aekdycoin擅长字符串问题和数论问题.当给定一个字符串s时,我们可以写下该字符串的所有非空前缀.例如:S:“ABAB”前缀是:“A”.“AB”.“ABA”.“ABAB”对于每个 ...
- 在github上创建新的分支(包括管理分支)
考虑到前面的项目在master分支上,这个不是太友好,下面在只有master分支的基础上,新建一个dev分支 一:查看 1.查看本地分支 git branch 2.查看远程分支 git branch ...
- html 水平竖直居中
line-height:容器高度 <!DOCTYPE html> <html lang="en"> <head> <meta charse ...
- 实现Java简单继承
面向对象练习-简单继承 一.完成教师类的创建 说明: id 代表身份证号 name 表示姓名 birth 表示出生日期 title 表示职称(讲师,副教授,教授等) 二.完成学生类的创建 说明: ma ...
- 20165220Java实验四 Android程序设计
一.实验报告封面 课程:Java程序设计 班级:1652班 姓名:葛宇豪 学号:20165220 指导教师:娄嘉鹏 实验日期:2018年5月14日 实验时间:13:45 - 15:25 实验序号:实验 ...
- python 配置导入方式
许多连接,如 from setting import redis_config pool= redis.ConnectionPool(**redis_config) r=redis.Redis(con ...
- Jenkins环境搭建(5)-与Jmeter完成参数化构建和构建前删除操作
此前介绍过几篇关于Jenkins配置相关的文章,今天再来说说参数化构建和构建前删除已有的报告.在实际测试过程中,是需要测试几套环境的,不使用参数化构建的话,构建脚本就比较麻烦了:自然,已生成的报告,不 ...
- P3810 -三维偏序(陌上花开)cdq-分治
P3810 [模板]三维偏序(陌上花开) 思路 :按照 1维排序 二维 分治三维树状数组维护 #include<bits/stdc++.h> using namespace std; #d ...
- linux 文件打包压缩成.tar.gz
tar czvf beian.drcluod.cn.20180509.tar.gz ./beian.drcloud.cn/*
- Vue常用V-标签
1.v-once: 只绑定一次 2.v-html <div v-html='name'></div> //会渲染标签 var app = new Vue({ el:'#app' ...