Ubuntu与stm32变量内存分配方式


一、用户空间内存分区

存储区图解
在这里插入图片描述

对各个内存分区的说明:

内存分区说明
程序代码区(code)存放函数体的二进制代码。一个C语言程序由多个函数构成,C语言程序的执行就是函数之间的相互调用。
文字常量区(constant)存放一般的常量、字符串常量等。这块内存只有读取权限,没有写入权限,因此他们的值在程序运行期间不能改变。
全局数据区(global data)存放全局变量、静态变量等。这块内存有读写权限,因此他们的值在程序运行期间可以任意改变。
堆区(heap)一般由程序员分配和释放,若程序员不释放,程序运行结束时由操作系统回收。malloc()、free()函数操作的就是这块内存。
动态链接库用于在程序运行期间加载和卸载动态链接库。
栈区(Stack)存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。
内核空间在程序拥有的独立空间中,分出一部分给操作系统内核使用,应用程序无法直接访问这一段内存,这一部分内存地址被称为内核空间(Kernel Space)。

在Windows下和Ubuntu系统下的内存模型如下(基于64位)

linux内存模型
在这里插入图片描述
Windows内存模型
在这里插入图片描述
不同于Linux,Windows是闭源的,有版权保护,资料较少,不好深入研究每一个细节,至今仍然有一些内部原理不被大家知晓。关于Windows地址空间的内存分布,官网上只给出了简单的说明:

  • 对于32位程序,内核占用较高的2GB,剩下的2GB分配给用户程序
  • 对于64位程序,内核占用最高的248TB,用户程序占用最低的8TB

参考:
Linux、Windows下C语言内存布局(内存模型)

接下来,让我们编写程序打印变量地址来比较其不同点

二、打印各变量地址信息

(一)代码

#include <stdio.h>
#include <stdlib.h>
//定义全局变量
int init_global_a = 1;       	 //.text段  
int uninit_global_a;            //.bss段  
static int inits_global_b = 2; //.text段  
static int uninits_global_b;  //.bss段 
void output(int a)
{
	printf("这是一个变量分配验证函数\n");
	printf("%d\n",a);
	printf("\n");
}

int main( )
{   
    //定义局部变量
    int a=2;   
    static int inits_local_c=2,  //.text段 
               uninits_local_c;  //.bss段 
    int init_local_d = 1;        //栈区 
    output(a);
    char *p;
    char str[10] = "lyy";       //栈区 

    //定义常量字符串
    char *var1 = "1234567890";  //文字常量区 
    char *var2 = "qwertyuiop";  //文字常量区 

    //动态分配
    int *p1=(int *)malloc(4);   //堆区 
    int *p2=(int *)malloc(4);   //堆区 

    //释放
    free(p1);
    free(p2);
    printf("栈区-变量地址\n");
    printf("                a:%p\n", &a);
    printf("                init_local_d:%p\n", &init_local_d);
    printf("                p:%p\n", &p);
    printf("              str:%p\n", str);
    printf("\n堆区-动态申请地址\n");
    printf("                   %p\n", p1);
    printf("                   %p\n", p2);
    printf("\n全局区-全局变量和静态变量\n");
    printf("\n.bss段\n");
    printf("全局外部无初值 uninit_global_a:%p\n", &uninit_global_a);
    printf("静态外部无初值 uninits_global_b:%p\n", &uninits_global_b);
    printf("静态内部无初值 uninits_local_c:%p\n", &uninits_local_c);
    printf("\n.data段\n");
    printf("全局外部有初值 init_global_a:%p\n", &init_global_a);
    printf("静态外部有初值 inits_global_b:%p\n", &inits_global_b);
    printf("静态内部有初值 inits_local_c:%p\n", &inits_local_c);
    printf("\n文字常量区\n");
    printf("文字常量地址     :%p\n",var1);
    printf("文字常量地址     :%p\n",var2);
    printf("\n代码区\n");
    printf("程序区地址       :%p\n",&main);
    printf("函数地址         :%p\n",&output);
    return 0;
}

(二)不同系统下运行

在Ubuntu下运行

在这里插入图片描述
在Windows下运行

在这里插入图片描述

三、MDK下程序的组成、存储与运行

(一)程序的存储

每次我们在使用MDK工具时,每次编译成功都会输出以下信息
在这里插入图片描述
在工程的编译提示输出信息中有一个语句“Program Size: Code=xx RO-data=xx RW-data=xx ZIdata=xx”,它说明了程序各个域的大小,编译后,应用程序中所有具有同一性质的数据 (包括代码) 被归到一个域,程序在存储或运行的时候,不同的域会呈现不同的状态,这些域的意义如下:

Code:即代码域,它指的是编译器生成的机器指令,这些内容被存储到 ROM 区。

• RO-data: Read Only data,即只读数据域,它指程序中用到的只读数据,这些数据被存储在ROM
区,因而程序不能修改其内容。例如 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 占用的空间值中包含了堆栈的大小 。

详见下图
在这里插入图片描述

(二)程序的运行

RW-data 和 ZI-data 它们仅仅是初始值不一样而已,为什么编译器非要把它们区分开?这就涉及 到程序的存储状态了,应用程序具有静止状态和运行状态。静止态的程序被存储在非易失存储器中,如STM32 的内部 FLASH,因而系统掉电后也能正常保存。但是当程序在运行状态的时候,程序常常需要修改一些暂存数据,由于运行速度的要求,这些数据往往存放在内存中(RAM),掉电后这些数据会丢失。因此,程序在静止与运行的时候它在存储器中的表现是不一样的。

详情见下图

在这里插入图片描述
图中的左侧是应用程序的存储状态,右侧是运行状态,而上方是 RAM 存储器区域,下方是 ROM存储器区域。

(三)在stm32上运行

在刚刚代码的基础上需要添加串口初始化函数,并将重定向printf()函数,在MDK中勾选Use MicroLIB

查看stm32地址的分配

在这里插入图片描述
打印各变量的地址信息

在这里插入图片描述


总结

有些时候,我们将一些稍微大一点的工程移植到Flash与RAM较小的开发板中,会出现堆栈溢出等错误;而有些时候,STM32硬件错误HardFault_Handler,也有可能是因为堆栈分配不合理、数组越界等。
掌握好STM32内存分配方式,对我们移植代码或者代码调试有很大的帮助;并且在嵌入式系统中,合理的分配变量,达到一个尽可能小的代码量也是极为重要的。

参考

《STM32库开发实战指南–基于野火指南者开发板》

基于ubuntu,树莓派和stm32的C程序的内存分配问题

Linux、Windows下C语言内存布局(内存模型)

【IoT】STM32 内存分配详解

基于STM32分析栈、堆、全局区、常量区、代码区、RAM、ROM

STM32 KEIL下的堆栈设置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值