arm 堆栈
来源:互联网 发布:淘宝订单改地址 编辑:程序博客网 时间:2024/05/25 05:37
先转一篇
http://blog.csdn.net/slj_win/article/details/16906141
关于堆和栈已经是程序员的一个月经话题,大部分有是基于os层来聊的。
那么,在赤裸裸的单片机下的堆和栈是什么样的分布呢?以下是网摘:
刚接手STM32时,你只编写一个
int main()
{
while(1);
}
BUILD://Program Size: Code=340 RO-data=252 RW-data=0 ZI-data=1632
编译后,就会发现这么个程序已用了1600多的RAM,要是在51单片机上,会心疼死了,这1600多的RAM跑哪儿去了,
分析map,你会发现是堆和栈占用的,在startup_stm32f10x_md.s文件中,它的前面几行就有以上定义,
这下该明白了吧。
Stack_Size EQU 0x00000400
Heap_Size EQU 0x00000200
以下引用网上资料 理解堆和栈的区别
(1)栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似
于数据结构中的栈。
(2)堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配
方式类似于数据结构中的链表。
(3)全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态
变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系
统自动释放。
(4)文字常量区:常量字符串就是存放在这里的。
(5)程序代码区:存放函数体的二进制代码。
例如:
int a=0; //全局初始化区
char *p1; //全局未初始化区
main()
{
int b; //栈
char s[]="abc"; //栈
char *p3= "1234567"; //在文字常量区Flash
static int c =0 ; //静态初始化区
p1= (char *)malloc(10); //堆区
strcpy(p1,"123456"); //"123456"放在常量区
}
所以堆和栈的区别:
stack的空间由操作系统自动分配/释放,heap上的空间手动分配/释放。
stack的空间有限,heap是很大的自由存储区。
程序在编译期和函数分配内存都是在栈上进行,且程序运行中函数调用时参数的传递也是在栈上进行。
------------------------------------------------------------------------------------------------------
1.堆和栈大小
定义大小在startup_stm32f2xx.s
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; Heap Configuration
; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
2.堆和栈位置
通过MAP文件可知
HEAP 0x200106f8 Section 512 startup_stm32f2xx.o(HEAP)
STACK 0x200108f8 Section 1024 startup_stm32f2xx.o(STACK)
__heap_base 0x200106f8 Data 0 startup_stm32f2xx.o(HEAP)
__heap_limit 0x200108f8 Data 0 startup_stm32f2xx.o(HEAP)
__initial_sp 0x20010cf8 Data 0 startup_stm32f2xx.o(STACK)
显然 Cortex-m3资料可知:__initial_sp是堆栈指针,它就是FLASH的0x8000000地址前面4个字节(它根据堆栈大小,由编译器自动生成)
显然堆和栈是相邻的。
3.堆和栈空间分配
栈:向低地址扩展
堆:向高地址扩展
显然如果依次定义变量
先定义的栈变量的内存地址比后定义的栈变量的内存地址要大
先定义的堆变量的内存地址比后定义的堆变量的内存地址要小
4.堆和栈变量
栈:临时变量,退出该作用域就会自动释放
堆:malloc变量,通过free函数释放
另外:堆栈溢出,编译不会提示,需要注意
------------------------------------------------------------------------------------------------------
如果使用了HEAP,则必须设置HEAP大小。
如果是STACK,可以设置为0,不影响程序运行。
IAR STM8定义STACK,是预先在RAM尾端分配一个字节的区域作为堆栈预留区域。
当程序静态变量,全局变量,或者堆与预留堆栈区域有冲突,编译器连接的时候就会报错。
你可以吧STACK设置为0,并不影响运行。(会影响调试,调试会报堆栈溢出警告)。
其实没必要这么做。
一般程序,(在允许范围内)设置多少STACK,并不影响程序真实使用的RAM大小,
(可以试验,把STACK设置多少,编译出来的HEX文件都是一样),
程序还是按照它原本的状态使用RAM,把STACK设置为0,并不是真实地减少RAM使用。
仅仅是欺骗一下编译器,让程序表面上看起来少用了RAM。
而设置一定size的STACK,也并不是真的就多使用了RAM,只是让编译器帮你
检查一下,是否能够保证有size大小的RAM没有被占用,可以用来作为堆栈。
以上仅针对IAR STM8.
------------------------------------------------------------------------------------------------------
从以上网摘来看单片机的堆和栈是分配在RAM里的,有可能是内部也有可能是外部,可以读写;
栈:存函数的临时变量,即局部变量,函数返回时随时有可能被其他函数栈用。所以栈是一种分时轮流使用的存储区,
编译器里定义的Stack_Size,是为了限定函数的局部数据活动的范围,操过这么范围有可以跑飞,也就是栈溢出;
Stack_Size不影响Hex,更不影响Hex怎么运行的,只是在Debug调试时会提示错。栈溢出也有是超过了国界进行
活动,只要老外没有意见,你可以接着玩,有老外不让你玩,你就的得死,或是大家都死(互相撕杀),有的人写
单片机代码在函数里定义一个大数组 int buf[8192],栈要是小于8192是会死的很惨。
堆:存的是全局变量,这变量理论上是所有函数都可以访问的,全局变量有的有初始值,但这个值不是存在RAM里的,是
存在Hex里,下载到Flash里,上电由代码(编译器生成的汇编代码)搬过去的。有的人很“霸道”,上电就霸占已一块很
大的RAM(Heap_Size),作为己有(malloc_init),别人用只能通过他们管家借(malloc),用完还得换(free)。所以
一旦有“霸道”的人出现是编译器里必须定义Heap_Size,否则和他管家借也没有用。
总之:堆和栈有存在RAM里,他两各分多少看函数需求,但是他两的总值不能超过单片机硬件的实际RAM尺寸,否则只能
到海里玩(淹死了)或是自己打造船接着玩(外扩RAM)。
下面是本博主的理解了。
几年前我理解51单片机的内存感觉弄得挺懂得。甚至对于简单的程序能够大概猜到内存分配的位置。这需要你对编译器的内存分配机制非常了解,比如对各种指针的优化所占的内存,所以说一般来讲编译器优化级别为0时候更容易猜出内存分配的具体地址。说起51的内存大概是先是通用寄存器组,然后是堆栈(这里就不深究堆栈的具体含义了)然后好像是几个bit的组合,然后到了栈顶。这样运行程序的时候就进栈出栈
。后来学了STM32,一直感觉堆栈弄得不太清楚,因为没用上OS,感觉没必要弄懂,就把STM32当成51用了。今天也不知道咋了,突然想知道栈指针在程序中的具体位置能不能捕捉到?这样我就知道程序运行有没有溢出啥的。
找了一会,发现在corte_cm3.C里面有__get_MSP()这个函数。这样就知道栈的位置了。
UINT32 value = 0;
value = __get_MSP();
函数内部是
/**
* @brief Return the Process Stack Pointer
*
* @return ProcessStackPointer
*
* Return the actual process stack pointer
*/
__ASM uint32_t __get_PSP(void)
{
mrs r0, psp
bx lr
}
当然我不会贸然调用这个函数,不知道当运行到 mrs r0, psp这句结束后,是否会被中断打断。C语言中用汇编我向来是及其谨慎的。当然这是在ARMCC编译器中的函数写法,在GCC编译器是别的写法,如下:
/**
* @brief Set the Process Stack Pointer
*
* @param topOfProcStack Process Stack Pointer
*
* Assign the value ProcessStackPointer to the MSP
* (process stack pointer) Cortex processor register
*/
void __set_PSP(uint32_t topOfProcStack) __attribute__( ( naked ) );
void __set_PSP(uint32_t topOfProcStack)
{
__ASM volatile ("MSR psp, %0\n\t"
"BX lr \n\t" : : "r" (topOfProcStack) );
}
然后我就再一次看到了启动代码。
事实上我现在才知道一般的裸奔(非操作系统)程序,在MDK的启动代码中,将堆和栈都设置为0都是可以运行的。
就是如下:
Stack_Size EQU 0x00000000
Heap_Size EQU 0x00000000
顺便插一句,注意一下字节对齐,如下:
Stack_Size EQU 0x00000000
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
事实上也是可以用C语言对齐的。或者在C语言中用关键字。
这个armcc编译器有个特点,和当年keil的51编译器不一样就是,在armcc编译器中你定义了占用内存的变量,比如全局变量,那么如果你程序中没有函数来调用这个变量那么就不分配内存。所以这样就会发生无论你把启动代码中的heap设置为多大,你在map文件中没有发现启动文件中提到的heap,如果你没有把你定义的内存的地址指向启动代码中的heap的话。[准确说应该heap是在启动代码中定义的一个全局变量,留给外面的C代码进行调用]。
事实上__get_MSP()的值也可以在debug的时候看到就在MDK调试界面的Registers的Banked下面有两个寄存器MSP和PSP.
好,下面写段代码
void hello(void)
{
Cunsigned char aa[0x300];
aa[0]++;
}
int main(void) {
while(1) {
hello();
}
}
第一步:
在进入hello函数时候,如下
void hello(void)
{ //<<=============黄色三角指向这里
UINT8 aa[0x300];
aa[0]++;
}
MDP值是200019D8
看一下反汇编,
注意这种情况下一定要单步运行才能看的清楚,
0x0800141A D1FB BNE 0x08001414
0x0800141C 4770 BX lr
62: {
63: UINT8 aa[0x300];
0x0800141E F5AD7D40 SUB sp,SP,#0x300/<<=============黄色三角指向这里
64: aa[0]++;
0x08001422 F89D0000 LDRB r0,[sp,#0x00]
0x08001426 1C40 ADDS r0,r0,#1
第二步:
再运行一个step oneline,则如下:
void hello(void)
{
UINT8 aa[0x300];
aa[0]++;//<<=============黄色三角指向这里
}
看一下界面的的MDP值是200016D8
0x0800141A D1FB BNE 0x08001414
0x0800141C 4770 BX lr
62: {
63: UINT8 aa[0x300];
0x0800141E F5AD7D40 SUB sp,SP,#0x300
64: aa[0]++;
0x08001422 F89D0000 LDRB r0,[sp,#0x00]//<<=============黄色三角指向这里
0x08001426 1C40 ADDS r0,r0,#1
第三步:step oneline 然后再steponeline
就跳出了hello函数发现界面MDP值是200019D8
通过以上看出来了,临时变量aa在栈上面进出的过程。
并且发现MDP的值变小又变大。
===========================================
常规来讲OS应该这样弄
最低地址是固定内存,如全局变量和static变量
然后是栈空间
然后是堆空间。
为什么ST公司的头文件要弄个堆的extern形式。
我现在觉得这个也挺有用的。我本来是想在RAM的最后开辟一个空间的。但是这样st的不同系列的单片机需要改不同的地址和空间大小,挺麻烦。这样我就可以在启动代码里面改这个空间大小了。而堆得起始地址直接就是分配完栈的空间紧挨的地址。
在来个思维跳跃,在启动代码的最后有:
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
写到如果定义了microlib这个要注意。因为在options for targets有个 use MicroCB选项。
好现在谈正事:
extern uint32 __heap_base;
extern uint32 __heap_limit;
uint32 * P; //这里不分析这个
uint8 * P1;
uint32 * P2;
int main (void){
P =(uint32 *) __heap_base;//这里不分析这个
P1 = (uint8 *) &__heap_base;
P2 = (uint32 *) &__heap_limit;
while(1);
}
运行完P2这句,发现MDK界面的MSP的值是0x20001B20
而P1和P2这两个地址也是0x20001B20。地址里面的值是几?当然是0啦。好像我以前博客讲过在启动代码里面调用了C函数将内存清零了。嗯 肯定是在启动代码里面调用的,是不是C函数忘记了,反正感觉是清零的比较隐蔽。
而51单片机的汇编启动代码可以清楚看到启动代码里面的汇编清零了RAM。
接着上面继续讲,就可以将P1或者P2作为堆得起始地址了。因为不同的存储器大小的STM32系列单片机有不同的启动文件,所以这样就改启动代码来配置堆的大小了。
但是我看像freertos代码把堆直接放到了固定内存区域也行。
===============================分界线=============
再转一篇
http://blog.csdn.net/love_gaohz/article/details/49276769
引言
这篇文章简要说说函数是怎么传入参数的,我们都知道,当一个函数调用使用少量参数(ARM上是少于等于4个)时,参数是通过寄存器进行传值(ARM上是通过r0,r1,r2,r3),而当参数多于4个时,会将多出的参数压入栈中进行传递(其实在函数调用过程中也会把r0,r1,r2,r3传递的参数压入栈),具体是什么实现的呢,我们看看。
函数栈
首先我们需要了解一下linux下一个进程的内存地址空间是如何布局的,在linux中,0~3G的虚拟地址为进程所有,3G~4G由内核所使用,每一个进程都有自己独立的0~3G内存地址空间。当进程进行函数调用时,我们都知道传入被调用函数的参数是通过栈进行操作的,这里我们只需要简单了解一下linux的内存地址空间中的栈是自顶向下生长的,就是栈底出于高地址处,栈顶出于低地址处。
好的,简单了解了内存地址空间的栈后,我们还需要简单了解一下EBP和ESP这两个寄存器,EBP是用保存栈低地址的,而ESP用于保存栈顶地址,而每一次函数调用会涉及到一个栈帧,栈帧结构如下图
举个实例详细说明一下一个函数帧的特点,比如
- /* B被A调用
- * 参数:data1, data2, data3
- * 局部变量: s1, s2, s3 */
- void B (int data1, int data2, int data3)
- {
- int b_s1;
- int b_s2;
- int b_s3;
- }
-
- /* A调用B函数 */
- void A (void)
- {
- int a_s1;
- int a_s2;
- int a_s3;
-
- B (1, 2, 3);
- printf ("1\n");
- }
在以上例子中栈帧情况应该如下图所示
从图例中可以看出,当A函数没有调用B函数时,A函数的栈帧只保存着局部变量,而EBP(栈底指针)指向的是A函数的函数栈帧头,而当A函数调用B函数时,A函数会将B函数所需要的参数从右往左压入栈(在例子中先压入3,之后是2,最后是1),之后会将A调用完B之后所需要运行的第一条指令压入栈,此时建立一个B的栈帧,具体流程:
- 从右往左将B函数所需参数压入栈
- 压入执行完B函数之后的第一条指令地址
- 建立B栈帧
- 压入A栈帧的栈底
- 压入B函数保护的寄存器
- 压入B函数的局部变量
小结
其实每一种处理器架构所使用的方式都不一样,在arm上我几个参数和不定参数的情况通过汇编代码查看又不相同,之后反汇编后研究透了会再发布一篇博文专门说这个,现在这篇就当做一个入门知识吧。
====================分界线=====================
博主再添加:
如果MDK 用了#include <stdlib.h>
就是用调用了malloc则map文件如下:
SystemCoreClock 0x20000014 Data 4 system_stm32f10x.o(.data)
AHBPrescTable 0x20000018 Data 16 system_stm32f10x.o(.data)
__microlib_freelist 0x200006ec Data 4 mvars.o(.data)
__microlib_freelist_initialised 0x200006f0 Data 4 mvars.o(.data)
__heap_base 0x20005530 Data 0 startup_stm32f10x_xl.o(HEAP)
__heap_limit 0x20005930 Data 0 startup_stm32f10x_xl.o(HEAP)
__initial_sp 0x20006130 Data 0 startup_stm32f10x_xl.o(STACK)
若没调用则如下:
SystemCoreClock 0x20000014 Data 4 system_stm32f10x.o(.data)
AHBPrescTable 0x20000018 Data 16 system_stm32f10x.o(.data)
__initial_sp 0x20005d28 Data 0 startup_stm32f10x_xl.o(STACK)
- ARM 堆栈
- ARM堆栈
- arm 堆栈
- ARM 堆栈溢出问题
- ARM堆栈方式
- ARM堆栈方式
- arm堆栈操作
- arm堆栈知识
- ARM汇编堆栈
- ARM 堆栈操作
- arm cortex 堆栈操作
- arm堆栈操作
- ARM中的堆栈形式
- ARM堆栈的分类
- ARM堆栈方式
- arm的堆栈结构
- arm堆栈的增长方式
- ARM的堆栈学习笔记
- [agc014c]Closed Rooms
- pwnable.kr 【fd】
- 短信轰炸机
- Unity Shader 学习笔记(10) 纹理(Texture)的属性
- 一些常用PHP语句,适用在ThinkPHP5上(1)
- arm 堆栈
- Android SpannableString---打造带样式的textview
- Spring Boot 事务的使用
- erlang中的错误处理
- Hibernate中的merge方法 以及对象的几中状态
- CSS Modules 用法教程
- JDBC--获得ResultSet的记录个数、字段个数(转载)
- Java并发编程75道面试题及答案——稳了
- LabVIEW串口通讯—通信协议