单片机中各类变量存储区域

在单片机中,申请的变量通常存放在内存中,具体存放位置取决于变量的类型、作用域以及存储方式。单片机的内存主要分为数据存储器(RAM)和程序存储器(ROM)两部分。

一、数据存储器(RAM)

1.1 栈区(stack):

.主要解释就是由编译器自动分配和释放,例如我们在某个函数内申请一个局部变量,这个变量会由编译器自动分配一个合适大小的栈区存储。

如下:

#include <stdint.h>
 
int main(void)
{
    uint8_t a; // 定义一个无符号的8位整型局部变量a

    return 0;
}

1.2 堆区(Heap):

.如果想将数据存入堆区,必须通过动态内存函数来实现,这些函数允许程序在运行时根据需要分配内存,而不是在编译时静态地分配。以下是如何在堆区申请数据的详细步骤和注意事项:

1.2.1 使用动态内存分配函数

malloc函数
  • malloc(memory allocation)函数用于在堆区分配指定大小的内存块。
  • 函数原型:void* malloc(size_t size);
  • 参数:size表示要分配的字节数。
  • 返回值:返回一个指向分配的内存块的指针,如果分配失败则返回NULL。
  • calloc函数
  • calloc(contiguous allocation)函数也用于在堆区分配内存,但它会自动将分配的内存块初始化为零。
  • 函数原型:void* calloc(size_t num, size_t size);
  • 参数:num表示要分配的元素个数,size表示每个元素的字节数。
  • 返回值:返回一个指向分配并初始化为零的内存块的指针,如果分配失败则返回NULL。
  • realloc函数
  • realloc(reallocation)函数用于调整之前分配的内存块的大小。
  • 函数原型:void* realloc(void* ptr, size_t size);
  • 参数:ptr是指向之前分配的内存块的指针,size是新的内存块大小。
  • 返回值:返回一个指向调整大小后的内存块的指针(可能与原指针不同),如果分配失败则返回NULL。注意,如果ptr为NULL,realloc的行为与malloc相同。
  • 使用示例:
  • #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        // 使用malloc分配内存
        // 分配一个可以存储10个整数的数组的内存空间
        int *arr = (int*)malloc(10 * sizeof(int));
        // 检查内存分配是否成功
        if (arr == NULL) {
            printf("内存分配失败\n");
            return 1; // 如果分配失败,返回1并结束程序
        }
    
        // 使用calloc分配并初始化内存
        // 分配一个可以存储5个浮点数的数组的内存空间,并将所有位初始化为0
        float *fArr = (float*)calloc(5, sizeof(float));
        // 检查内存分配是否成功
        if (fArr == NULL) {
            printf("内存分配失败\n");
            free(arr); // 释放之前已经分配的内存
            return 1; // 如果分配失败,返回1并结束程序
        }
    
        // 使用realloc调整内存大小
        int newSize = 20; // 新的数组大小
        // 重新分配arr指向的内存,使其可以存储20个整数
        int *newArr = (int*)realloc(arr, newSize * sizeof(int));
        // 检查内存重新分配是否成功
        if (newArr == NULL) {
            printf("内存重新分配失败\n");
            free(fArr); // 释放之前已经分配的内存
            free(arr);  // 在realloc失败的情况下,原内存仍然有效且需要释放
            arr = NULL; // 将arr设置为NULL,避免悬挂指针(指向已释放内存的指针)
            return 1; // 如果重新分配失败,返回1并结束程序
        } else {
            arr = newArr; // 更新arr指针,使其指向新的内存地址
        }
    
        // 使用分配的内存(示例)
        // 初始化arr数组的每个元素为其索引的平方
        for (int i = 0; i < newSize; i++) {
            arr[i] = i * i;
        }
    
        // 释放内存
        free(arr); // 释放arr指向的内存
        free(fArr); // 释放fArr指向的内存
    
        return 0; // 程序正常结束,返回0
    }
  • 注意事项:
  • 内存泄漏:动态分配的内存必须在使用完毕后显式地释放,否则会导致内存泄漏。
  • 悬挂指针:在释放内存后,不要继续使用指向该内存的指针,否则会导致悬挂指针错误。
  • 内存碎片:频繁的分配和释放内存可能会导致内存碎片问题,影响内存的使用效率。
  • 对齐和边界:确保分配的内存大小满足对齐要求,并避免越界访问。
  • 错误处理:始终检查动态内存分配函数的返回值,以确保内存分配成功。
  • 1.3全局/静态变量数据区

  • 初始化的全局变量和静态变量存放在一块区域。
  • 未初始化的全局变量和静态变量在相邻的另一块区域(通常被初始化为0或特定的默认值)。
  • 全局变量和静态变量(包括外部变量和内部静态变量)通常存放在这里。
  • 这些变量的生命周期是整个程序的运行期间。
  • 使用示例:
    
    uint8_t a=0;    //初始化全局变量
    uint8_t b;      //未初始化全局变量
    static uint8_t  c=0;    //初始化静态变量
    static uint8_t  d;       //未初始化静态变量 
    int main(void)
    {
    
    
    }

    ┌────────────────────────────────────────────────────┐

    │                                                       单片机存储区                                                  │ │ ┌──────────────────────────────────────────────┐

    │ │ │                                                 程序存储区(Flash等)                        │ │ │ └──────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────┐

    │ │ │                                               数据存储区                                 │ │ │ │

    ┌──────────────────────────────────────────┐

    │ │ │ │ │             已初始化的全局/静态变量区                                    │ │ │ │ │ └──────────────────────────────────────────┘ ┌──────────────────────────────────────────┐

    │ │ │ │ │         未初始化的全局/静态变量区                                    │ │ │ │ │ └──────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────┘ 

    二、程序存储器(ROM)

  • 程序存储器主要用于存放程序的二进制代码。
  • 常量字符串或只读数据也可以存放在程序存储器中(通常称为RO-data区)。比如const修饰的变量就是放在ROM中。
  • 三、特殊存储区

  • 寄存器(register)
  • 翻阅很多资料我也没得到一个确切的答案,本人目前直接使用指针操控寄存器,所以不多说这个。

  • 外部存储器(External memory)
  • 对于使用各类通信接口,可以外部扩展存储数据的存储器,我定义为外部存储器,具体包括EEPROM、FLASH、SDRAM等。

    可以使用IIC,SPI等通讯协议操控读写数据。

    四、keil编译器输出说明

    map文件内容:

    如上图所示:

    Code是代码占用的空间;
    RO-data是 Read Only 只读常量的大小,如const型;
    RW-data是(Read Write) 初始化了的可读写变量的大小;
    ZI-data是(Zero Initialize) 没有初始化的可读写变量的大小。ZI-data不会被算做代码里因为不会被初始化;

    FLASH占用大小为:Code + RO Data + RW Data 

    RAM占用大小为:RW Data + ZI Data

    值得一提的是,有的MCU内部集成了SDRAM作为UI运行的显示缓存,一般是2M/4M/8M/16M,这个时候ZI-data会加上UI运行时的缓存,如下:

    map文件内容:

    可以看到RW Size 非常庞大,无法计算出实际占用RAM大小,这个时候可以网上寻找RW_IRAM1(单片机不同,这个名称也不同)的位置,算出实际变量占用RAM的大小,如下:

     Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x000570e0, Size: 0x0000bab0, Max: 0x00010000, ABSOLUTE, COMPRESSED[0x00000140]),

    大约46K。

    五、单片机中注意事项:

  • 内存管理:在单片机编程中,由于资源有限,因此需要特别注意内存的管理和使用。
  • 变量作用域和生命周期:在定义变量时,需要明确变量的作用域和生命周期,以避免不必要的内存浪费和访问冲突。
  • 存储类型选择:根据变量的使用情况和单片机的内存资源,选择合适的存储类型(如内部RAM、外部存储器等)。
  • 编译器和硬件平台:不同的编译器和硬件平台可能对变量的存储和管理有不同的要求和限制。因此,在编程时需要参考具体的编译器和硬件平台的文档。
  • 作者:sakuraifapig

    物联沃分享整理
    物联沃-IOTWORD物联网 » 单片机中各类变量存储区域

    发表回复