关于栈和堆的一些资料。
首先是从理论上的东西。。网上转载来的,后面是看AVR代码时得出的一些东西。
硬件堆栈:或许也可以称作系统堆栈,是位于片内RAM区。有人说,只要能使用PUSH,POP指令的单片机,都可以说含有硬件堆栈。这样的说法我个人觉得不是很全面。通过指令进行压栈和出栈操作只是系统堆栈中的一种操做。系统堆栈还可以被隐含调用。例如,当调用子程序时,系统会主动把返回地址压入堆栈,并不需要用户通过指令操作。通常,栈底设在内存的高端,也就是把内存的最高一段空间划作栈区。这些都是向下生长栈。栈指针可能是专用的寄存器,也可能借用一通用寄存器。也有单片机是在数据区里划一块作栈区,可能是向上生长,也可能是向下生长。
硬件堆栈:是通过寄存器SPH,SPL做为索引指针的地址,是调用了CALL,RCALL等函数调用指令后硬件自动填充的堆栈!
软件堆栈:是编译器为了处理一些参数传递而做的堆栈,会由编译器自动产生和处理,可以通过相应的编译选项对其进行编辑。
简单一点说,硬件堆栈主要做为地址堆栈用,而软件堆栈主要会被分配成数据堆栈!
---摘自《AVR单片机C语言开发入门指导》- P169---
ICCAVR 使用两个堆栈:一个用于子程序调用和中断操作的硬件堆栈,一个用于传递参数、临时变量和局部变量的软件堆栈。可以使用堆栈检测函数检测两个堆栈是否溢出。
如果没有硬堆栈,你可以选定一个寄存器作堆栈指针,通过软件实现堆栈操作。移植μC/OS-II也不一定要硬堆栈。ARM就很难说它的堆栈是软的还是硬的。32位的ARM指令中没有PUSH、POP指令。ARM习惯上用R13作堆栈指针(SP),但用别的寄存器作堆栈指针也未常不可。ARM习惯上用LDM/STM(多寄存器加载/存储指令)来操作堆栈,压多少,按什么顺序都能选择。应该说ARM是软硬结合的堆栈。
C代码(AVR-GCC编译,优化等级-00):
#include <avr/io.h>
int add(int a,int b)
{
int c;
c=a+b;
returnc;
}
int main(void)
{
inta=2,b=3,c=0;
c=add(a,b);
//c=sub(a,b);
}
汇编代码:
(省略一些boot代码)
。。。。。。。
00000054 <__ctors_end>:
54: 1124 eor r1,r1
56: 1fbe out 0x3f,r1 ;63
58: cfe5 ldi r28,0x5F ;95 //此处Y指针和SP都指到了SRAM最高端
5a: d4e0 ldi r29,0x04 ;4
5c: debf out 0x3e,r29 ;62
5e: cdbf out 0x3d,r28 ; 61
。。。
0000008e <add>:
#include <avr/io.h>
int add(int a,int b)
{
8e: cf93 push r28
90: df93 push r29 //保存了Y指针,此时SP已经-2,这里再减2
92: cdb7 in r28,0x3d ;61 //重新定位Y指针跟SP一样。
94: deb7 in r29,0x3e ;62
96: 2697 sbiw r28,0x06 ;6 //减掉6,即向下开了6字节的区域,存放3变量
98: 0fb6 in r0,0x3f ;63
9a: f894 cli
9c: debf out 0x3e,r29 ;62
9e: 0fbe out 0x3f,r0 ; 63
a0: cdbf out 0x3d,r28 ;61
a2: 9a83 std Y+2,r25 ;0x02
a4: 8983 std Y+1,r24 ;0x01
a6: 7c83 std Y+4,r23 ;0x04
a8: 6b83 std Y+3,r22 ;0x03
int c;
c=a+b;
aa: 2981 ldd r18,Y+1 ;0x01
ac: 3a81 ldd r19,Y+2 ;0x02
ae: 8b81 ldd r24,Y+3 ;0x03
b0: 9c81 ldd r25,Y+4 ;0x04
b2: 820f add r24,r18
b4: 931f adc r25,r19
b6: 9e83 std Y+6,r25 ;0x06
b8: 8d83 std Y+5,r24 ;0x05
returnc;
ba: 8d81 ldd r24,Y+5 ;0x05
bc: 9e81 ldd r25,Y+6 ;0x06
be: 2696 adiw r28,0x06 ;6 //加了6个字节空间,Y指针恢复到减6之前
c0: 0fb6 in r0,0x3f ;63
c2: f894 cli
c4: debf out 0x3e,r29 ;62
c6: 0fbe out 0x3f,r0 ; 63
c8: cdbf out 0x3d,r28 ;61
ca: df91 pop r29
cc: cf91 pop r28
ce: 0895 ret //弹出堆栈中2个字节
000000d0 <main>:
}
int main(void)
{
d0: c9e5 ldi r28,0x59 ;89 //这4句给SP和Y指针重新赋值了,很明显的在SP的
d2: d4e0 ldi r29,0x04 ;4 //上面还有6个字节(SRAM最大到045E),这6个字节
d4: debf out 0x3e,r29 ;62 //被存放了a,b,c三个变量(可以与上面理论对应)
d6: cdbf out 0x3d,r28 ;61 //通过Y指针来保存了这三个变量到这个区域
inta=2,b=3,c=0;
d8: 82e0 ldi r24,0x02 ;2
da: 90e0 ldi r25,0x00 ;0
dc: 9a83 std Y+2,r25 ;0x02
de: 8983 std Y+1,r24 ;0x01
e0: 83e0 ldi r24,0x03 ;3
e2: 90e0 ldi r25,0x00 ;0
e4: 9c83 std Y+4,r25 ;0x04
e6: 8b83 std Y+3,r24 ;0x03
e8: 1e82 std Y+6,r1 ;0x06
ea: 1d82 std Y+5,r1 ;0x05
c=add(a,b);
ec: 6b81 ldd r22,Y+3 ;0x03
ee: 7c81 ldd r23,Y+4 ;0x04
f0: 8981 ldd r24,Y+1 ;0x01
f2: 9a81 ldd r25,Y+2 ;0x02
f4: 0e 94 4700 call 0x8e<add> //使用call时自动将PC+2的地址压到堆栈
f8: 9e83 std Y+6,r25 ;0x06
fa: 8d83 std Y+5,r24 ;0x05
//c=sub(a,b);
}
fc: 80e0 ldi r24,0x00 ;0
fe: 90e0 ldi r25,0x00 ;0
100: 0c 94 82 00 jmp 0x104<_exit>
00000104 <_exit>:
104: ffcf rjmp .-2 ; 0x104<_exit>
r28和r29一起组成SP指针,Y指针可以作为间接寻址,很明显的刚开始的时候Y指针和SP都在045F这里,后来在高处开了6个字节的空间来存放临时变量,所以Y指针成了这个软件堆栈的栈顶,在这个过程中都是使用Y和SP的配合来实现变量和数据的改变,以及恢复,硬件堆栈和软件堆栈在这里已经不怎么区分了。。。不清楚流程可以画个图来加深理解,好了,看了那么久,总算有点感觉了。。
栈是可变的,要留足够的空间才行,如果没有操作系统用的会很少,主要取决于函数的嵌套深度参数类型。
一般情况RAM存放三种类型的数据:
1.全局变量
2.堆(典型的MALLOC函数调用),这个得看你用了还是没有
3.栈,这个必然要用到的,有操作系统的话用的就更多了,每一个任务都会有一个栈,根据任务的函数嵌套程度可分配不同的大小。
具体要看什么编译器了,所以首先要估计一下你的栈要用多少,然后,再计算一下你的全局变量有多少,最后定一下可能的动态分配内存(堆)有多少就可以了。