谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data( 其中有的可能分析得不太正确,希望可以得到大佬们的指点纠正)



浅谈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”,它说明了程序各个域的大小,编译后,应用程序中所有具有同一性质的数据(包括代码)被归到一个域,程序在存储或运行的时候,不同的域会呈现不同的状态编译后的输出结果
谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第1张图片

首先来解释哈各表示什么含义

  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 文件
谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第2张图片
谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第3张图片
  编译后发现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值异常,尽然找到原因了,那我就来测测它是怎么分配的堆栈的大小:
谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第4张图片 谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第5张图片
  通过这样测试好像是没得戏啊,反正是看不出来的,我们在启动文件中配的是栈空间 1024 Byte、堆空间 512 Byte,总和为:1536 Byte,而ZI-data = 1632, 这样测不出来,只能说上述的测试超出了堆栈空间,编译器是检测不了的,只能是在执行程序时无法正常运行

   值得注意的堆栈的溢出,程序会进入一个中断里面时循环

  我们怎么解决这个问题,主要还是出在了没定义:__MICROLIB
  显然在 .c 文件中是不可以的
   由于IDE 它有这个功能,所以可这样操作:(哈哈哈,当然你也可以暴力一点去启动文件吧 IF 判断给去掉,有点血腥)
谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第6张图片

这就对了,由于没有使用到堆,所以被编译器优化了ZI-data = 1024刚好是栈的大小(正确)谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第7张图片

感兴趣你可以去研究哈自己实现 __use_two_region_memory 怎么写,可以去这里查它该怎么写:
谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第8张图片

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第9张图片

来测测 RO-data 区域:主要测试const 关键字修饰的变量

Test01:

const unsigned char test = 1;

int main()
{	
}
void SystemInit(){
}

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第10张图片

编译结果显示:RO-data 没有变化,进行Test02 测试

Test02:

const unsigned char test = 1;

int main()
{
	if( test == 1 ){
		//	// 打开 GPIOB 端口的时钟
		*( unsigned int * )0x40021018 |=  ( (1) << 3 );
	}
}
void SystemInit(){
	
}

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第11张图片

编译结果显示:RO-data 没有变化,进行Test03 测试

Test03:

const unsigned char test[1] = { 1 };

int main()
{	
}
void SystemInit(){
}

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第12张图片

编译结果显示:RO-data 没有变化,进行Test04 测试

Test04:

const unsigned char test[1] = { 1 };

int main()
{
	if( test[0] == 1 ){
		//	// 打开 GPIOB 端口的时钟
		*( unsigned int * )0x40021018 |=  ( (1) << 3 );
	}
}
void SystemInit(){
	
}

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第13张图片

  编译结果显示: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(){
}

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第14张图片

编译结果显示:哈? RO-data 又回去了, 怎么回事? 在进行测试Test06

Test06:

int main()
{
	const unsigned char test[2] = { 1 };
	if( test[0] == 1 ){
		//	// 打开 GPIOB 端口的时钟
		*( unsigned int * )0x40021018 |=  ( (1) << 3 );
	}
}
void SystemInit(){
}

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第15张图片

  编译结果显示: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(){
}

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第16张图片

编译结果显示: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谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第17张图片

Test09谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第18张图片

  编译结果显示: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, ; 测试结果

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第19张图片

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 还是测不来变化,只能验证到这儿了

在这里插入图片描述

RW-data --ZI-data 测试

int a = 1;
int main()
{
	if( a == 1)
	{
		*( unsigned int * )0x40021018 |=  ( (1) << 3 );
	}
}
void SystemInit(){
}

测试结果: RW-data 增加了

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第20张图片

  最好还是别有单个的测,都用数组测且数组的大小为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

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第21张图片

Test12:

unsigned char a[1024] = { 0};
int main()
{
	
	if( a[0] == 1)
	{
		*( unsigned int * )0x40021018 |=  ( (1) << 3 );
	}
}

void SystemInit(){
}

测试结果:ZI-data 增加了1024

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第22张图片

  当然其中还有RO-data 发生变化,当RW-data 变化的RO-data 也有轻微的变化,这个可能和编译器有关,不做研究

在 ZI-data 添加上堆的大小

#include <stdlib.h>

int main()
{
	unsigned char * p;
	
	p = (unsigned char *)malloc(10);
}
void SystemInit(){
}

  测试结果:ZI-data = 1024 + 512 = 1536,刚好为栈空间 + 堆空间的大小,这里只要用到了堆空间,他就会把整个堆空间的大小计算进来

谈谈Keil-MDK编译输出的:Code-data,RO-data,RW-data,ZI-data_第23张图片

最后温馨提示:如果定义了没有用到的变量,也就是比较冗余的变量的(编译器这么觉得),很有可能被编译器优化,自己注意一下

 
 
 
 

Code-data,RO-data,RW-data,ZI-data,程序运行时的加载过程

你可能感兴趣的:(STM32,嵌入式系统,STM32,MDK,Keil,Code,RW-RO-ZI-data)