谷动谷力

标题: 嵌入式开发如何统计运行占据内存 [打印本页]

作者: 鸣涧    时间: 2022-12-1 08:58
标题: 嵌入式开发如何统计运行占据内存
嵌入式开发如何统计运行占据内存


1、各抒己见2、笔者分析

笔者来说说看法,经过试验得出的结果,以ARMCC、IAR以及GCC为例

2.1 ARMCC 分析

以一个例程来分析,led.c 最简单的

u32 LEDValue1 = 0XFFFF;
const u32 LEDValue2=0XFFFF;
u32 LEDValue3[4];

void LED_Init(void)
{      
  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOF时钟
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE);

  //GPIOF9,F10初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12 |GPIO_Pin_13| GPIO_Pin_14;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//上拉
  GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化

  GPIO_Write(GPIOD,LEDValue1);  //以下代码都是为了测试
  GPIO_Write(GPIOE,LEDValue2);
  LEDValue3[0]=0XFFFF;
  GPIO_Write(GPIOE,LEDValue3[0]);
}
void LedRun()
{
  GPIO_ResetBits(GPIOD,GPIO_Pin_5);
  GPIO_SetBits(GPIOD,GPIO_Pin_5);
}
1234567891011121314151617181920212223242526272829303132
2.1.1 笔者A观点

然后打开生成的map文件,可以看到具体编译好的信息,很方便分析单个.o文件所占用的size信息,比如该文件led.c

从上面Map信息里面看到Led.o的size情况:

所以如果算法单独使用了一个.o文件,在armcc下,很容易分析出数据的空间使用大小。但是栈的空间+堆的空间没有统计到,

堆是运行态的,静态编译出来的无法统计到,需要具体的情况具体分析,单独去看malloc这种,或者自己内存管理的空间申请。

至于栈的使用空间,编译阶段可能不知道,因为编译阶段不知道调用关系,而链接的时候则由链接器将多个.o文件组织起来,所以可以知道调用关系,

接着来分析一下,为啥LED_Init函数的栈使用了24Byte空间。

大家都知道,数据的运算以及函数的调用,都会用到寄存器,而用寄存器之前需要保存寄存器,所以栈主要是用来保存该函数用到的寄存器,来看一下汇编,很容易就明白了。

push的时候,都是4字节对齐的(寄存器都是32位的),所以总共push了6个寄存器,总共24Byte。

push {r2-r6,r14}
1

从上文可以看到LED.o共使用了156Byte,

从map文件中来看,两个函数分别是116Byte + 24Byte,共140Byte,由上文可知,共156Byte,那么16Byte就是上文中的inc.data,就是Code中用到了一些数据,这些数据无法直接访问,需要开辟一块单元来存储这些数据地址,然后才可以加载。如下面第二张图所示,比如0x40021000,很明显这个就不是Code的地址或者RAM的地址,就是一个外设地址(GPIOE),根据STM32的手册可以得知(下面图三)。

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

上文中RW Data 或者ZI Data 符合预期,但是RO Data 很奇怪是0,因为我们本身定义了一个const的类型的数据,但是统计竟然是0,

u32 LEDValue1 = 0XFFFF;
const u32 LEDValue2=0XFFFF;
u32 LEDValue3[4];
123

这个需要从汇编入手,编译器也不傻,定义一个const 类型的数据,编译器会就生成一个RO data吗,不一定,比如本文这个,编译器直接将0xFFFF 编译到指令中,而不是从变量中加载数据,这个需要从汇编中看。

如何才能产生一个RO data呢?如果引用到变量的地址,那么肯定会产生一个RO data,因为需要分配变量地址。例如下文中这样。

  u32 *  data_p = (u32*)&LEDValue2;
  *data_p = *data_p +1;
  GPIO_Write(GPIOE,LEDValue2);
123

然后分析map文件,可以看到RO data Size 为4,led.o中有了RO的变量以及地址,也可以看到ro data的地址不是在sram,而是在flash中(ROM)中,最后汇编也为const 变量申请了存储空间(LEDValue2)

2.1.2 B同学观点

对于B同学的观点,我基本 都是赞同的。补充一下:就是所需要的内存,可能还需要加上Code所需要的空间(如果有这种场景的话,在内存中允许代码)

对于栈是动态的理解,我的想法也是栈是动态变化的

map文件是看不到局部变量的,原因有两点,

map文件中看到的 全局变量 或者局部静态变量。

2.1.2 C同学观点

对于C同学的观点,很多我都有不同的意见,

上面那个LedRun函数可能没有局部变量,那我们来加一个局部变量来看看,例如下面的代码,如果包括局部变量,那么函数的size一定会超过100,毕竟还有指令的size,实际编出来 的map文件分析,看到函数大小为64,分析汇编代码,指令数也是64,可以得出结论,函数的大小是不包括局部变量的。

void LedRun()
{
  u16 LEDData[100]={123};
  u8 i=0;
  for(i=0;i<100;i++)
    GPIO_Write(GPIOE,LEDData); //测试,可能没意义
  GPIO_ResetBits(GPIOD,GPIO_Pin_5);
  GPIO_SetBits(GPIOD,GPIO_Pin_5);
}
123456789

o文件的大小 包括了 code、data(RO RW ZI)的大小,也没包括局部变量的大小。

但是函数本身使用的局部变量空间是可以统计出来的,刚刚也看到了,通过链接器生成的信息。

216 = (1002)+ Push(44 寄存器r4 r5 r6 r14)

举例说明,本程序的栈空间是0x400,还是刚刚的程序,同样可以编译过,没有报任何错误,还统计出来栈的使用情况(2064> 0x400(1024))。

void LedRun()
{
  u16 LEDData[0x400]={123};
  u16 i=0;
  for(i=0;i<0x400;i++)
    GPIO_Write(GPIOE,LEDData); //测试,可能没意义
  GPIO_ResetBits(GPIOD,GPIO_Pin_5);
  GPIO_SetBits(GPIOD,GPIO_Pin_5);
}
123456789
在这里插入图片描述

以上就是笔者分析的一些情况,有不同分意见可以分享评论。后面简单以IAR以及arm-gcc 分析,看看是否有所不同。

附录:









欢迎光临 谷动谷力 (http://bbs.sunsili.com/) Powered by Discuz! X3.2