内存映射

在一些桌面程序中,整个内存映射是通过虚拟内存来进行管理的,使用一种称为内存管理单元(MMU)的硬件结构来将程序的内存映射到物理RAM。在对于 RAM 紧缺的嵌入式系统中,是缺少 MMU 内存管理单元的。因此在一些嵌入式系统中,比如常用的 STM32 来讲,内存映射被划分为闪存段(也被称为Flash,用于存储代码和只读数据)和RAM段,用于存储读写数据。

STM32 的 Flash 和 RAM 地址范围

笔者标题所说的内存是指 STM32 的 Flash 和 RAM,下图是 ARM Cortex M3 的地址映射图:



从图中我们可以看到 RAM 地址是从 0x2000 0000 开始的,Flash地址是从 0x0800 0000 开始的,笔者将在下文中着重对这两部分进行剖析。

Flash

代码和数据是存放在 flash 中的,下面是将 flash 内部进行细分之后的一张图,图中标明了代码段,数据段以及常量在 flash 中的位置。



如上图所示,Flash 又可以细分为这么几个部分,分别是文本段 (Text),其中文本段中又包含可执行代码 (Executable Code)和常量 (Literal Value),在文本段之后就是只读数据区域 (Read Only Data),当然并不是所有架构的单片机都满足这样一个排布规律,这里只针对ARM Cortex M3 系列,只读数据段后面接着的就是数据复制段 (Copy of Data Section),第一次遇到这个概念的朋友看到数据复制可能会有所疑惑,其实这个段充当的作用是存放程序中初始化为非 0 值的全局变量的初始值,之所以要将初始值存放到这里,是因为全局变量是存放在 RAM 上的,RAM 上的值掉电便丢失,每次上电后这些变量是要进行重新赋值的,而重新赋的值就存放在这里。那为什么不存放初始化为 0 的全局变量初始值呢,原因也很简单,既然是初始化为 0,那么在上电后统一对存放初始化为 0 的全局变量的那块区域清0就好了。下面举一个例子分析各个变量在上述中的存储位置:

#include <stdio.h>
const int read_only_variable = 2000;
int data = 500; void my_function(void)
{
int x = 200;
char *str = "string";
}

在上述代码中,read_only_variable 是一个用 const 修饰的全局变量,它是只读的,存放在 flash 中的只读数据区域,编译器会给 read_only_variable 分配一个地址,并将 2000 这个数据存放到这个位置。data 这个变量将存放到 RAM 中的RW区域中 (后面将会进行详细讲解),但是 data 后面的初始值 500 将会被存放到数据复制区域中, 也就是上图中从下往上的第三个区域。在 my_function 中的变量 x 将会被存放到 RAM 中的堆栈中,将 x 赋值为 200 ,200 将被存储到 flash 里的 Text 中的常量区 (Literal Valu) 中。str 是一个 char 型的指针变量,它指向的是字符串第一个字符存放的位置,然而对于字符串 string 来讲,它是存放在Text常量区的,所以指针变量指向这个区域的一个地址,但是因为它终归中局部变量,它指向 Flash 的一个地址,但是其本身还是存放于 RAM 中的堆栈上的。

RAM

STM32单片机的片内RAM会被链接文件“分区”为如下几个段:



如上图所示,RAM 中包含了如下几个部分:

  • 栈 (Stack) : 存放局部变量和函数调用时的返回地址
  • 堆 (heap) : 由 malloc 申请,由 free 释放
  • bss : 存放未初始化或者是初始化为 0 的全局变量
  • data : 存放初始化为非 0 值的全局变量

下面举一个简单的例子来说明变量在各个段中的存储位置:

#include <stdio.h>
#include <stdlib.h>
int data_var = 500;
int bss_var0;
int bss_var1 = 0;
static int static_var; void my_function(void)
{
static int static_var1 = 0;
int stack = 0;
char *buffer;
const int value = 1;
buffer = malloc(10);
}

上述变量的命名已经很清楚地表明了变量处于 RAM 中的哪一个段,data_var 是已经初始化的全局变量,存放在 RAM 的 data 区,bss_var0bss_var1是未初始化和初始化为0的全局变量,他们都存放于 RAM 中的 bss段,由 static 修饰的static_varstatic_var1 都存放于 bss段,区别只在于两个变量的作用域不同。stack 是在函数内部定义的局部变量,其存放于 RAM 的区域,用 const 修饰的局部变量 value ,虽然他是只读的,但是它是存储于 RAM 中的中的,这里也说明一点,并不是所有用 const 修饰的变量都是存放于只读变量区的。buffer指针变量用 malloc 函数申请了 10 字节的内存空间,那这10字节的内存空间位于中。

堆栈溢出

如果在程序运行的过程中,堆的空间也一直在消耗,同时栈的空间也在增加,那么这时堆和栈如果碰到一起,那么就会造成堆栈溢出,从而导致我们的程序跑飞。

STM32中的map文件分析

在用 keil 编译 STM32 工程之后,我们会得到一个 map 文件,map 文件的最底部有这么一个信息:



上图中的各个段是和上文所述是能够进行对应起来的,正如下面这张表所示:

Code RO Data RW Data ZI Data
Executable Code Read Only Data data bss

总结

对于 RAM 和 flash 空间都有限的 MCU 来讲,了解各个变量在内存中的存储位置是很有必要的,他能够很好地帮助我们去解决很多问题。

最后如果您觉得我的文章对您有所帮助,欢迎关注我的个人公众号:wenzi嵌入式软件

STM32 内存分配解析及变量的存储位置的更多相关文章

  1. 内存分配、C++变量的生命周期和作用域

    1.内存分配 程序的内存分配有以下几个区域:堆区.栈区.全局区.程序代码区,另外还有文字常量区. 栈区 ——存放局部变量,即由auto修饰的变量,一般auto省略.由编译器自动分配释放.局部变量定义在 ...

  2. jvm源码解读--02 Array<u1>* tags = MetadataFactory::new_writeable_array<u1>(loader_data, length, 0, CHECK_NULL); 函数引入的jvm内存分配解析

    current路径: #0 Array<unsigned char>::operator new (size=8, loader_data=0x7fd4c802e868, length=8 ...

  3. Java学习笔记9---类静态成员变量的存储位置及JVM的内存划分

    笔记8提到了类静态成员变量的访问方式,但静态成员变量存储在哪里呢?在网上查阅不少资料,发现好多内容都是过时的了,其中主流观点是静态成员变量存放在方法区.JDK8之前,静态成员变量确实存放在方法区:但J ...

  4. 【转载】java 中变量的存储位置

    原文链接点这里,感谢博主分享 * 寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. * 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出 ...

  5. java中变量的存储位置

    1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符 ...

  6. C++中类所占的内存大小以及成员函数的存储位置

    类所占内存的大小是由成员变量(静态变量除外)决定的,虚函数指针和虚基类指针也属于数据部分,成员函数是不计算在内的.因为在编译器处理后,成员变量和成员函数是分离的.成员函数还是以一般的函数一样的存在.a ...

  7. Python3基础 使用id() 查询变量的存储位置

    镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...

  8. C内存分配函数

    C语言跟内存分配方式(1) 从静态存储区域分配.内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在.例如全局变量,static变量.(2) 在栈上创建.在执行函数时,函数内局部变量的 ...

  9. C语言中内存分配 (转)

    在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要介绍内存管理基本概念,重 ...

随机推荐

  1. vue中的生命周期事件和钩子函数

    vue实例有一个完整的生命周期,也就是从开始创建.初始化数据.编译模板.挂载Dom.渲染->更新->渲染.卸载等一系列过程,我们称这是vue的生命周期.通俗的将就是vue实例从创建到销毁的 ...

  2. Sql练习201908200916

    表结构: 一条sql修改多条数据(Sql server),oracle,mysql请自行尝试: ; go 完成.

  3. WEB安全——XML注入

    浅析XML注入 认识XML DTD XML注入 XPath注入 XSL和XSLT注入 前言前段时间学习了.net,通过更改XML让连接数据库变得更方便,简单易懂,上手无压力,便对XML注入这块挺感兴趣 ...

  4. SpringAOP入门

    Spring的AOP aop概述 Aspect Oriented Programing 面向切面(方面)编程, aop:扩展功能不修改源代码实现 aop采取横向抽取机制,取代了传统纵向继承体系重复性代 ...

  5. c期末笔记(2)

    1.定义数组 1.1.a[3][2] = [1,2,3,4,5,6],代码是定义一个三行两列的二维数组.在数组声明和初始化时,如果用户定义的元素数量超过用户规定的元素数量,以语法错误报错.(如:cah ...

  6. Kylin 新定位:分析型数据仓库

    亲爱的各位社区朋友: Apache Kylin 在 2014 年 10 月开源并加入 Apache 软件基金会的孵化器,一年后从孵化器毕业成为 Apache 顶级项目.从第一天起,Kylin 的标语是 ...

  7. php--一些有用的Laravel辅助函数

    str_start()/str_finish() 将指定值添加到字符串的开头/结尾(当不是以该值开头/结尾时) blank() 判断给定的值是否为「空」 collect() 根据给定的数组创建一个集合 ...

  8. 批处理 bat 查询局域网内在线电脑IP

    查看自己局域网的IP和物理网卡地址可以在 WIN+R –> 打开cmd 键入 arp -a 可以看到局域网中所有的在线IP COLOR 0A CLS @ECHO Off Title 查询局域网内 ...

  9. python3的subprocess的各个方法的区别(二)

    subprocess如何避免死锁 如果交互是双工的,即涉及读取和写入,则尤其如此.这种交互可能导致死锁,因为两个进程都可能最终等待另一个进程的输出 您希望从子进程标准输出管道读取,但标准错误管道的缓冲 ...

  10. Java第二十九天,文件及目录的管理,File类

    一.基础知识点 1.路径分隔符 (1)什么是路径分隔符? 这个多被应用在环境变量设置当中,例如当我设置Path环境变量时,多个环境变量的路径要用 ':'(Windows系统用封号分隔)或 ':'(Li ...