浅谈Keil-MDK创建项目&编译过程
Code-data,RO-data,RW-data,ZI-data 程序运行时加载过程
程序存储时占用的ROM区大小(内部Flash):
Code + RO-data + RW-data
程序执行时的只读区域(RO)(Flash) :Code + RO data
程序执行时的可读写区域(RW)(SRAM) :RW data + ZI data
在工程的编译提示输出信息中有一个语句“Program Size:Code = xx RO-data = xx RW = data =x x ZI-data = xx”,它说明了程序各个域的大小,编译后,应用程序中所有具有同一性质的数据(包括代码)被归到一个域,程序在存储或运行的时候,不同的域会呈现不同的状态编译后的输出结果
Code:即代码域,它指的是编译器生成的机器指令,这些内容被存储到ROM 区(也就是STM32 的内部 Flash 中)
RO-data:Read Only data,即只读数据域,它指程序中用到的只读数据,这些数据被存储在 ROM 区( STM32 内部 Flash ),因而程序不能修改其内容。例如 C 语言中 const 关键字定义的变量就是典型的 RO-data
RW-data:Read Write data,即可读写数据域,它指初始化为“非 0 值”的可读写数据,程序刚运行时,这些数据具有非 0 的初始值,且运行的时候它们会常驻在 RAM 区,因而应用程序可以修改其内容。例如 C 语言中使用定义的全局变量
,且定义时赋予“非 0 值”
给该变量 进行初始化
ZI-data:Zero Initialie data,即 0 初始化数据,它指初始化为“0 值”的可读写数据域,它与 RW-data 的区别是程序刚运行时这些数据初始值全都为 0,而后续运行过程与 RW-data 的性质一样,它们也常驻在 RAM 区,因而应用程序可以更改其内容。例如 C 语言中使用定义的全局变量,且定义时赋予“0 值”给该变量进行初始化(若定义该变量时没有赋予初始值,编译器会把它当 ZI-data 来对待,初始化为 0)
ZI-data 的栈空间(Stack)及堆空间(Heap):在 C 语言中,函数内部定义的局部变量属于栈空间,进入函数的时候从向栈空间申请内存给局部变量,退出时释放局部变量,归还内存空间。而使用 malloc 动态分配的变量属于堆空间。在程序中的栈空间和堆空间都是属于 ZI-data 区域的,这些空间都会被初始值化为 0 值。
(这里值得注意的是栈和堆里面的没初始化的数据是随机数,自己仔细斟酌,通过测试 ZI-data的大小的确等于堆栈的大小之和)
编译器给出的 ZI-data 占用的空间值中包含了堆栈的大小(经实际测试,若程序中完全没有使用 malloc 动态申请堆空间,编译器会优化,不把堆空间计算在内)综上所述,以程序的组成构件为例,它们所属的区域类别如图:
程序组件 所属类别 机器指令 Code 常量 RO-data 初始非0的全局变量 RW-data 初值为0的全局变量 ZI-data 局部变量 ZI-data 栈空间 使用malloc动态分配的空间 ZI-data 堆空间
首先建工程包含一个启动文件和main.c 文件
编译后发现ZI-data = 1632 ,这数据看着很是奇怪啊,这是为什么了,程序没有使用任何变量及数据数据,它的大小应该为栈的大小才对啊,而启动文件中是这样设置的栈:
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00000400 ; 0x400 = 1024 Byte
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200 ; 0x200 = 512 Byte
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
也就是 ZI-data 应该等于 1024 才对,是什么原因导致ZI-data的数据异常的了?接着分析,由于堆栈的初始化是通过 C 库函数 __main 来完成的(这点啊是不会出错的),那么就去到启动文件里面的堆栈初始化时怎么处理的,启动文件中有如下代码:
;******************************************************************************* ; User Stack and Heap initialization ;******************************************************************************* IF :DEF:__MICROLIB EXPORT __initial_sp EXPORT __heap_base EXPORT __heap_limit ELSE IMPORT __use_two_region_memory EXPORT __user_initial_stackheap __user_initial_stackheap LDR R0, = Heap_Mem LDR R1, =(Stack_Mem + Stack_Size) LDR R2, = (Heap_Mem + Heap_Size) LDR R3, = Stack_Mem BX LR ALIGN ENDIF END
首先一来他就判断了一个宏定义(__MICROLIB ),然而我们是没有定义这个宏定义的,故它将走到else后面的语句,好家伙 else后一来就是 IMPORT __use_two_region_memory 而这个函数得由用户自己实现(实现起来也不是那么容易,),我们在工程也没有实现过它 ( 但是他也没报错,你把IMPORT 理解为 C 的 extern ),所以,ZI-data值异常,尽然找到原因了,那我就来测测它是怎么分配的堆栈的大小:
通过这样测试好像是没得戏啊,反正是看不出来的,我们在启动文件中配的是栈空间 1024 Byte、堆空间 512 Byte,总和为:1536 Byte,而ZI-data = 1632, 这样测不出来,只能说上述的测试超出了堆栈空间,编译器是检测不了的,只能是在执行程序时无法正常运行
值得注意的堆栈的溢出,程序会进入一个中断里面时循环
感兴趣你可以去研究哈自己实现 __use_two_region_memory 怎么写,可以去这里查它该怎么写:
Test01:
const unsigned char test = 1;
int main()
{
}
void SystemInit(){
}
编译结果显示:RO-data 没有变化,进行Test02 测试
Test02:
const unsigned char test = 1;
int main()
{
if( test == 1 ){
// // 打开 GPIOB 端口的时钟
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
编译结果显示:RO-data 没有变化,进行Test03 测试
Test03:
const unsigned char test[1] = { 1 };
int main()
{
}
void SystemInit(){
}
编译结果显示:RO-data 没有变化,进行Test04 测试
Test04:
const unsigned char test[1] = { 1 };
int main()
{
if( test[0] == 1 ){
// // 打开 GPIOB 端口的时钟
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
编译结果显示:RO-data 发生变化,这里能看出的是如果你定义的变量没使用的话编译器很有可能给你优化了,那么为什么 const unsigned char test[1] = { 1 }; 的数据放到 RO-data,而 const unsigned char test = 1 ;没有变化了?在进行测试 Test05
Test05:
int main()
{
const unsigned char test[1] = { 1 };
if( test[0] == 1 ){
// // 打开 GPIOB 端口的时钟
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
编译结果显示:哈? RO-data 又回去了, 怎么回事? 在进行测试Test06
Test06:
int main()
{
const unsigned char test[2] = { 1 };
if( test[0] == 1 ){
// // 打开 GPIOB 端口的时钟
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
编译结果显示:RO-data 发生变化,这里能看出的是 const unsigned char test[1] = { 1 }; RO-data 不变,而 const unsigned char test[2] = 1 ;RO-data 增加 ,这又是为何? 在进行测试 Test07
Test07:
int main()
{
const unsigned char test = 1 ;
if( test == 1 ){
// // 打开 GPIOB 端口的时钟
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
编译结果显示:RO-data 没有变化 所以我也是一串 ?????,测试Test08,Test09
Teat08:
const unsigned char test[ 1024 * 1024 ] = { 1 };
int main()
{
if( test[0] == 1 ){
// // 打开 GPIOB 端口的时钟
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
Test09:
int main()
{
const unsigned char test[ 1024 * 1024 ] = { 1 };
if( test[0] == 1 ){
// // 打开 GPIOB 端口的时钟
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
编译结果显示:Test08炸了,已经远超了,flash,然后Test09完好无事,(通过这些测试表明,const修饰的变量会在RO-data区域, 但是有些为什么RO-data不变化?这可能与编译器的处理有关,我也问号一片,有待研究)
当然测试出现的这些问题也不能影响 只读数据在 RO-data 区域
再抛一个神奇的测试:
#define N 7
int main()
{
const char str[N] = "123456";
}
void SystemInit(){
}
N = 1,2, ; 测试结果
N = 3, 4 ; 测试结果
N = 5, 6 , 7, 8; 测试结果
N = 9, 10, 11, 12; 测试结果
哈,似乎发现了什么规律,再测一哈全局的
#define N 4
const char str[N] = "1";
int main()
{
if( str[0] == '1')
{
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
N = 1,2; 测试结果
N = 3,4; 测试结果
N = 9, 10; 测试结果
测试结果都表示,RO-data 按4的倍数增加,所以可以得出有的const 变量 RO-data,没有变化可能是前面的还有剩余空间,或许还与对齐有关,既然这样我们在用4字节的变量来测试验证哈
Test10:
#define N 10
const int str[N] = {1};
int main()
{
if( str[0] == 1)
{
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
测试结果:RO-data = 252 + 4 * 10 = 292, 果然我们获或许猜的不错, 但是把单独的const int 还是测不来变化,只能验证到这儿了
int a = 1;
int main()
{
if( a == 1)
{
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
测试结果: RW-data 增加了
最好还是别有单个的测,都用数组测且数组的大小为4的倍数(unsigned char xxx[1024]最好)效果比较好
Test11:
unsigned char a[1024] = { 0 , 1 , 2};
int main()
{
if( a[0] == 1)
{
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
测试结果:RW-data 增加了1024
Test12:
unsigned char a[1024] = { 0};
int main()
{
if( a[0] == 1)
{
*( unsigned int * )0x40021018 |= ( (1) << 3 );
}
}
void SystemInit(){
}
测试结果:ZI-data 增加了1024
当然其中还有RO-data 发生变化,当RW-data 变化的RO-data 也有轻微的变化,这个可能和编译器有关,不做研究
#include <stdlib.h>
int main()
{
unsigned char * p;
p = (unsigned char *)malloc(10);
}
void SystemInit(){
}
最后温馨提示:如果定义了没有用到的变量,也就是比较冗余的变量的(编译器这么觉得),很有可能被编译器优化,自己注意一下