接触了两年多时间的单片机编程本人对关于单片机程序内存如何耗费的问题一直懵懵懂懂,直到在近日看到某篇有关于MDK MAP文件介绍的帖子后才有种醍醐灌顶的感觉,这里我将分享在此之上的观点与见解以供大家讨论学习。
大家都知道ARM单片机的内部存储空间极其匮乏无论是从Flash还是RAM上,每每给单片机机编程都有一种惜字如金的感觉,工程师们一般会在容量有限的情况下规范其编程习惯简化代码避免冗余,那么首先我们如何知道的程序下载到单片机上到底占用了多少Flash程序运行又会使用多少RAM?
首先上一张图:
该图是KEIL编译某个单片机程序后生成的构建信息,这里我们只关注其中的Program Size信息。
Program Size其意义是编译后生成的代码大小单位是字节,Program Size的大小有四大决定因素:Code,RO-data,RW-data,ZI-data 。
Code :意义是代码指令占用的空间;
RO-data :是Read Only Data的缩写,意义是只读常量占用的空间。如const型常量,常量字符串等等;
RW-data :是Read Write Data的缩写,意义是可读可写的已初始化 了的变量占用的空间。如全局变量,静态变量等等;
ZI-data :是Zero Initialize Data的缩写,意义是以0初始化的变量。如未初始化赋值的全局变量,静态变量等等;
综上来说 烧写的时候是FLASH中的被占用的空间大小为:Code + RO Data + RW Data,而程序运行的时候数据使用到的RAM的空间大小为:RW Data + ZI Data。
Why?
FLASH中的被占用的空间很好理解,就是等于代码指令+只读数据的值+已初始化变量的值。那么运行时数据占用RAM空间大小==RW Data + ZI Data又作何理解?
我们都知道,在代码运行机制上单片机不同于PC,单片机的程序通常是在FLASH中直接取指执行,而PC是先把程序拷贝到RAM中再取指执行。
由上说明单片机的RAM中至少不会存在Code拷贝(除非使用了特殊方法强行使程序拷贝到了RAM中执行,本贴不讨论此情况),
其次,单片机RAM中也不会存在RO-data拷贝,因为RO-data是只读数据,为了节省RAM空间,这种数据在执行时直接从FLASH中取出使用,无需再复制到RAM。
那么剩下的 RW Data + ZI Data由于是可读可写的数据,为了能够供程序运行时正常读写,于是就会被放在单片机的RAM中(单片机的FLASH区不能被程序改写)。
有人可能会问RW-data 与ZI-data都是指的全局变量或者静态变量,那么程序中的局部变量去了哪儿?这里就要向大家澄清一个事实,在C或C++中全局变量或 静态变量在RAM中都有一个特定地址(存在于静态区),而局部变量却没有特定的地址
因为局部变量存在于栈中(存在于堆栈区),当函数入栈时系统就会在栈顶之上开辟一段内存供给局部变量使用,当函数出栈时该内存就会被释放掉。
那么单片机在程序运行时RAM的使用量就等于RW Data + ZI Data了吗,还有没有其他因素会导致RAM占用变化?
玩过PC的都知道,一个程序在运行时它在内存中的占用情况是会随时改变的,这其中可能有压栈入栈和堆块的申请与释放等事件发生,那么在单片机里难道就没有这样的过程了吗?答案都是否定的。
单片机的RAM中也有堆栈区,那么程序运行时RAM的使用量就不会再等于RW Data + ZI Data了,因为程序的堆栈区也是一段具体的内存,那么堆栈区的内存占用又有多大?
堆栈区大小的查询方法,这里以STM32F1系列作为介绍,以航迹雲STF1驱动集合库中的startup_stm32f10x_hd.s启动文件为例:
(看不清图片的朋友们,请右键查看图片)
图中有个Stack_Size(栈大小)与Heap_Size(堆大小)定义,这两十六进制的数值之和就是你的单片机运行时RAM中的堆栈字节大小,其他单片机平台的堆栈大小查询方法请自行百度。
以上可以得出,一个arm架构的单片机的程序在运行时将会占用到的RAM空间等于 RW Data + ZI Data + Stack_Size + Heap_Size;
那么在程序运行时还有没有其他因素会导致RAM被使用的空间发生变化?
在回答这个问题之前,我们先来讨论何为 “在编译时编译器会为一个变量分配一段内存” ?玩过汇编的都知道编译器并不会给变量一个内存, 而是编译器在内存中为变量指定了一个地址而已,然后让其他变量不会重复指向该地址,在编译时编译器会把变量名由地址替换掉,这样就达到了貌似"编译器给变量分配了内存" 的效果。
因为编译器为每个变量分配地址且不会让该地址被占用,由此得知RW Data + ZI Data这两块数据在被分配好内存之后会一直处于无法被回收的状态,根据认知科学一一无法回收==占用,所以RAM中的RW Data + ZI Data区就会一直处于占用状态,如果没有新的程序烧录进来RW Data + ZI Data区占用的空间是不会变的。
那么堆栈区就更不用说了,堆栈区的大小是由单片机的启动文件中Stack_Size 与 Heap_Size确定的,函数入栈出栈变的只是堆栈区内的数据而不是变化堆栈区大小( 注:如果函数入栈的数量超出了堆栈区的大小限制则为爆栈),如果没有新的程序烧录进来Stack_Size + Heap_Size区占用的空间也是不会变的。
结论:ARM单片机中的FLASH的占用量取决于Code + RO Data + RW Data,程序运行时RAM的占用量取决于 RW Data + ZI Data + Stack_Size + Heap_Size且在程序运行过程中该占用量几乎不变;
这是我的第一篇博客,以上皆为个人愚解,如有不周欢迎斧正。
2017/9/11 航迹雲