NotePublic/Software/Development/Theory/嵌入式程序开发_存储器类型与存储区划分_字符串与RAM...

96 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
layout: post
title: "嵌入式程序开发——存储器类型与存储区划分字符串与RAM占用"
subtitle: ""
description: "为便于深入理解和开发嵌入式软件,本文对常见存储区划分进行了说明,并对不同类型存储器所存储的内容进行了介绍。"
excerpt: "本文对常见嵌入式系统程序的存储区和分类进行的对比说明,关于程序存储区的划分也使用用其他程序。"
date: 2020-05-27 14:40:00
author: "Rick Chan"
tags: ["Development", "Theory","存储"]
categories: ["Software"]
published: true
---
首先说一下 MCU 的存储器组织。
到本文发布为止MCU 中常使用的存储器类型有FLASH、RAM、ROM包括EEPROM
在软件角度来看,程序和数据的存储分为以下几个部分:
![程序和数据的存储划分](./img/嵌入式程序开发_存储器类型与存储区划分_字符串与RAM占用/001.png)
**注:**
1. 代码段和常量段都可以用于保存常量数据,其主要区别是,如果常量可以作为汇编指令的一个操作数,则该常量被编译进代码段。如果不能用一个汇编操作数表示,则存于常量段。如 "uchar a=0x05;" 中的 "0x05" 将被编译成代码 "mov #0x05 a";如果是 "uchar a[]={0x05 0x06}" 则 "0x050x06" 被放置于常量段, 在初始化 a[] 的时候会有一段汇编指令用于将常量段中的内容拷贝到 a[] 中。
软件存储区与硬件存储器类型是怎么对应的呢?
一般来讲如下:
![软件存储区与硬件存储器类型对应关系](./img/嵌入式程序开发_存储器类型与存储区划分_字符串与RAM占用/002.png)
**注:**
1. MCU 中的 ROM 通常用于存储制造商信息、控制器型号等信息;
2. 对于 x86 体系结构的系统,因为没有 Flash 类型的存储器,所以,所有的软件存储区最终都加载到内存中,但是其内存是分段的,用户对不同内存段的访问权限不同,其代码段和常量段不可以被用户修改,如果意外修改则抛出段错误异常。
知道了存储器类型和各存储区的划分之后,让我们来看以下三组程序:
```cpp
/**
* Progrm 1
* 相当于汇编伪代码: mov 0x313233343500, &Str[0]
*/
static void ProcStr(void)
{
char Str[] = {"12345"};
}
```
这段程序中Str[] 是一个局部数组,其大小为 6占用的堆栈空间是 8 个字节假设按4字节对齐"12345" 是立即数相当于代码的一部分被存储在代码段Str[] 的初始化过程,相当于从代码段拷贝 6 个字节的数据到栈中,这 6 个字节的 C 语言表示是 "12345\0"。
```cpp
/**
* Program 2
* 相当于汇编伪代码: mov 0x313233343500, &Str[0]
*/
static void ProcStr(void)
{
char Str[] = "12345";
}
```
这段程序与 Progrm 1 的本质是一样的Str[] 是一个局部数组,其大小为 6占用的堆栈空间是 8 个字节假设按4字节对齐"12345" 是立即数相当于代码的一部分被存储在代码段Str[] 的初始化过程,相当于从代码段拷贝 6 个字节的数据到栈中,这 6 个字节的 C 语言表示是 "12345\0"。
```cpp
/**
* Program 3
* 相当于汇编伪代码:
* mov 0xZYX, Str
* section .rodata:
* 0xZYX:
* 0x31,0x32,0x33,0x34,0x35,0x00
*/
static void ProcStr(void)
{
const char* Str = "12345";
}
```
这段程序中没有数组,唯一的 Str 是一个局部指针,其大小为 4在 32 位系统中),因此这段程序只占用 4在 32 位系统中) 个字节的堆栈空间;"12345" 是常量被存储在常量段Str 的初始化过程,是将指针 Str 初始化为常量 "12345" 的地址,后续程序通过指针 Str 直接访问常量段,无需内存拷贝过程。
**注:**
1. 每段程序的注释中有等同的汇编伪代码;
2. 字符 '1' 的 Hex 值是 0x31字符 '2' 的 Hex 值是 0x32...,字符 '5' 的 Hex 值是 0x35
3. '\0' 的 Hex 值是 0x00
4. const 关键字并不影响汇编结果。
从以上分析可以看出,前两种方法是一样的,都需要为局部数据分配存储空间,并将代码段中的立即数拷贝过来,而最后一种方法是通过指针直接访问静态数据而无需拷贝。如果字符串长度大于系统中指针的长度,第三种方法将极大的节省堆栈空间。
对于 MCU 来说,这三种方法都需要访问存储在 Flash 中的数据,根据分析,前两种方法应该在读取指令的同时加载立即数,而第三种方法需要进行至少一次额外的 Flash 访问来获取数据,从速度上来说前两者可能更优(访问 Flash 的速度要比内存拷贝慢)。如果接下来要频繁使用这个数据,那么 Cache 机制将对第三种写法有很大的影响。
当然,如果在接下来的程序中,需要修改字符串 Str 中的内容,那就只能采用前两种方法,而第三种方法的数据存储在只读存储区,不可以进行写操作。
*特殊说明:本文的一些内容跟编译器特性和具体的处理器相关,不同编译器,甚至相同编译器的不同版本间可能存在一定差异。*