C language + microcontroller - detailed explanation of memory distribution, the most complete on the entire network, worth saving

       

Table of contents

1. C language memory partition

1. Code area

2. Constant area

3. Global (static) area

4. Heap

5. stack

2. STM32 memory allocation

1. Random access memory—RAM

2. Read-only memory—ROM

3. Verification based on STM32 code

1. The detailed code is as follows

2. The running results are as follows

4. Memory distribution in microcontroller

1. Explanation of meaning

2. Program storage distribution

3. The program occupies the space of Flash and SRAM

5. Reasons for dividing each paragraph (essence)

1. Why are the "code segment" and "data segment" of the program stored separately?

2. Why is it so troublesome to divide data segments into .data, .bss, and .rodata? What's the difference?

3. Why are global variables still divided into initialized and uninitialized?


        This article mainly talks about the memory distribution in C language and microcontroller.

1. C language memory partition

The schematic diagram of C language memory partition is as follows:

ddab81176bcfdfda0d35f898913149d0.png

1. Code area

  • The program execution code is stored in the code area, and its value cannot be modified (an error will occur if modified).

  • String constants and constants defined by define may also be stored in the code area.

2. Constant area

  • Constants such as strings and numbers are stored in the constant area.

  • Global variables modified by const are stored in the constant area.

  • While the program is running, the contents of the constant area cannot be modified.

3. Global (static) area

Introduction to global (static) area

  • The compiler allocates memory when compiling, and the storage of global variables and static variables is put together. In C language, initialized global variables and static variables are in one area, and uninitialized global variables and uninitialized static variables are in another adjacent area.

  • The global area consists of .bss segment and .data segment, which is readable and writable.

.bss section

  • Uninitialized global variables and uninitialized static variables are stored in the .bss section.

  • Global variables initialized to 0 and static variables initialized to 0 are stored in the .bss section.

  • The .bss section does not occupy executable file space, and its content is initialized (cleared) by the operating system.

.data section

  • Initialized global variables are stored in the .data section.

  • Initialized static variables are stored in the .data section.

  • The .data section occupies executable file space, and its content is initialized by the program.

4. Heap

Introduction to heap area

  • The heap area is allocated and released by the programmer.

  • The heap area grows from low address to high address according to the memory address. What is allocated using malloc, calloc, realloc and other memory allocation functions is on the heap.

Note: Remember to release the heap memory to avoid memory leaks.

Call functions

  • Use functions such as malloc to dynamically distribute memory.

void *malloc(size_t);

The parameter size_t is the allocated byte size.

The return value is a void pointer that points to the first address of the allocated space.

(void * pointers can be converted to pointers of other types at will)

  • Use the free function to release memory, otherwise it will cause memory leaks.

void free(void * /ptr/);

The parameter is the first address of the allocated memory.

5. stack

Introduction to stack area

  • The stack area is automatically allocated and released by the compiler and automatically managed by the operating system, without manual management.

  • The contents on the stack area only exist within the scope of the function. When the function ends, these contents will be automatically destroyed.

  • The stack area grows in the direction of memory addresses from high addresses to low addresses. Its maximum size is determined at compile time. It is fast, but has poor flexibility and a small maximum space.

  • The stack area is based on the first-in, last-out principle, that is, those who enter first are blocked in the innermost part of the room, those who enter last are at the door, and those at the door go out first when they are released.

Store content

  • Temporarily created local variables and local variables defined by const are stored in the stack area.

  • When a function is called and returned, its entry parameters and return value are stored in the stack area.

2. STM32 memory allocation

1. Random access memory—RAM

  • RAM is an internal memory that directly exchanges data with the CPU, also called main memory (memory).

  • It can be read and written at any time, is very fast, and is often used as a temporary data storage medium for the operating system or other running programs.

  • RAM cannot retain data when the power is turned off (the data disappears when the power is turned off). If you need to save data, you must write them to a long-term storage device (such as a hard disk).

2. Read-only memory—ROM

  • The data stored in ROM is generally written in advance before being installed into the whole machine. It can only be read out during the operation of the whole machine, and cannot be rewritten quickly and conveniently like random access memory.

  • The data stored in ROM is stable and will not change even after the power is turned off.

This article uses the STM32F103 chip. The default memory configuration in the keil V5 environment is shown below:

fa731f684c18ff29863e5d492a4c1ee2.png

  • The ROM area starts at 0x8000000 and has a size of 0x10000. This area is a read-only area and cannot be modified. It stores the code area and constant area.

  • The RAM area starts at 0x20000000 and has a size of 0x5000. This area is a readable and writable area and stores the global (static) area, heap area and stack area.

The internal partitions of the chip are shown below:

8083bb18c6c9025c5e28a9ccb39f2db9.png

3. Verification based on STM32 code

1. The detailed code is as follows

#include <string.h>
#include <stdio.h>
#include <stdlib.h>



//全局区
int q1;            //未初始化全局变量
static int q2;     //未初始化静态变量    
const int q3;      //未初始化只读变量        

int m1 = 1;        //已初始化全局变量
static int m2 = 2; //已初始化静态变量

//常量区
const int m3 = 3;  //已初始化只读变量
                
int main(void)
{
     SystemCoreClockUpdate(); 

     
     while(1)
    {
        //栈区    
        int mq1;               //未初始化局部变量
        int *mq2;              //未初始化局部指针变量

        int mq3=3;             //已初始化局部变量    
        char qq[10] = "hello"; //已初始化局部数组

        const int mq4;        //未初始化局部只读变量
        const int mq5=3;      //已初始化局部只读变量

        //堆区
        int *p1 = malloc(4);  //已初始化局部指针变量p1
        int *p2 = malloc(4);  //已初始化局部指针变量p2        

        //全局区
        static int mp1;        //未初始化局部静态变量    
        static int mp2 = 2;    //已初始化局部静态变量

        //常量区
        char *vv = "I LOVE YOU";//已初始化局部指针变量
        char *mq = "5201314";

        printf("\n栈区-变量地址\n");
        printf("未初始化局部变量 :0x%p\r\n",&mq1);
        printf("未初始化局部指针变量 :0x%p\r\n",&mq2);
        printf("已初始化局部变量 :0x%p\r\n",&mq3);
        printf("已初始化局部数组 :0x%p\r\n", qq );
        
        printf("未初始化局部只读变量 :0x%p\r\n",&mq4);
        printf("已初始化局部只读变量 :0x%p\r\n",&mq5);
        
        printf("\n堆区-动态申请地址\r\n");
        printf("已初始化局部int型指针变量p1 :0x%p\r\n", p1);
        printf("已初始化局部int型指针变量p2 :0x%p\r\n", p2);
        
        printf("\n全局区-变量地址\n");
        printf("未初始化全局变量 :0x%p\r\n",&q1);
        printf("未初始化静态变量 :0x%p\r\n",&q2);
        printf("未初始化只读变量 :0x%p\r\n",&q3);
        
        printf("已初始化全局变量 :0x%p\r\n",&m1);
        printf("已初始化静态变量 :0x%p\r\n",&m2);
        
        printf("未初始化局部静态变量 :0x%p\r\n",&mp1);
        printf("已初始化局部静态变量 :0x%p\r\n",&mp2);            
        
        printf("\n常量区地址\n");
        printf("已初始化只读变量 :0x%p\r\n",&m3);
        printf("已初始化局部指针变量 :0x%p\r\n",vv );
        printf("已初始化局部指针变量 :0x%p\r\n",mq );
        
        printf("\n代码区地址\n");
        printf("程序代码区main函数入口地址 :0x%p\n", main);

        delay_ms(1000);

        free(p1);
        free(p2);        
    }
}

2. The running results are as follows

栈区-变量地址
未初始化局部变量       :0x20000654
未初始化局部指针变量   :0x20000650
已初始化局部变量       :0x2000064c
已初始化局部数组       :0x20000640
未初始化局部只读变量   :0x2000063c
已初始化局部只读变量   :0x20000638

堆区-动态申请地址
已初始化局部指针变量p1 :0x20000060
已初始化局部指针变量p2 :0x20000068

全局区-变量地址
未初始化全局变量       :0x20000014
未初始化静态变量       :0x20000018
未初始化只读变量       :0x2000001c
已初始化全局变量       :0x20000020
已初始化静态变量       :0x20000024
未初始化局部静态变量   :0x20000028
已初始化局部静态变量   :0x2000002c

常量区地址
已初始化全局只读变量   :0x080011a4
已初始化局部指针变量   :0x08000e78
已初始化局部指针变量   :0x08000e84

代码区地址
程序代码区main函数入口地址 :0x08000d6d

4. Memory distribution in microcontroller

        After compiling the program in keil, in the bottom "Build Output" output column, you can see a line of information like "Program Size: Code=321676 RO-data=35972 RW-data=4788 ZI-data=126284". This sentence provides the size information of the program after compilation, including the size of Code , RO-data , RW-data and ZI-data . This size information has the following implications for developers and embedded system designers:

  1. Memory optimization : By understanding the sizes of different parts, you can evaluate the memory usage of the program and help with memory optimization. For example, you can save system resources by reducing memory usage by reducing code size, optimizing data storage methods, or optimizing algorithms.

  2. Memory planning : Understanding the Code, RO-data, RW-data, and ZI-data sizes of the program can help with memory planning. In embedded system design, memory resources are limited, so memory space needs to be allocated reasonably to meet the needs of the program. By knowing the sizes of different sections, you can determine how they are laid out and allocated in memory to avoid conflicts or waste.

  3. System reliability : Proper management of memory is crucial to system reliability. By understanding the size of the program, especially the size of RO-data and RW-data, you can ensure that the memory space used does not exceed the available memory range of the device and avoid memory overflows and related errors.

  4. Debugging and troubleshooting : The information provided by Program Size can be used for debugging and troubleshooting. If there are problems while the program is running, such as crashes or performance issues, you can locate the problem by checking the size and memory usage of different parts and analyze whether there are memory-related errors or bottlenecks.

        It can better help developers better manage memory resources in embedded system design and ensure that the program runs normally on the target device.

1. Explanation of meaning

Code : Code segment refers to the executable machine instructions generated by the program compiler.

  • Storage location: ROM

  • Features: Read only

RO-data : Data segment refers to the read-only data part of the program, including constants, strings, const-defined variables, etc.

  • Storage location: ROM

  • Features: Read only

RW-data : Data segment refers to readable and writable data initialized to a "non-zero value". When the program is running, these data will reside in the RAM area, and the application can modify its content. Includes global and static variables initialized to non-zero.

  • Storage location: both ROM and RAM

  • Features: Readable and writable

ZI-data : Data segment refers to readable and writable data initialized to a value of 0. The difference between it and RW-data is that the initial values ​​of these data are all 0 when the program is just running. When the program is running, the properties are the same as RW-data. They also reside in RAM areas and applications can change their contents. Includes global and static variables that are uninitialized and initialized to zero.

  • Storage location: RAM

  • Features: Readable and writable

Expand one:

ZI-data’s stack space (Stack) and heap space (Heap) :

        In C language, local variables defined inside a function belong to the stack space. When entering the function, memory will be applied to the stack space for the local variables. When exiting, the local variables will be released and the memory space will be returned. Variables dynamically allocated using malloc belong to the heap space. The stack space and heap space in the program belong to the ZI-data area, and these spaces will be initialized to 0 values. The space value occupied by ZI-data given by the compiler includes the size of the stack (after actual testing, if malloc is not used to dynamically apply for heap space at all in the program, the compiler will optimize and not include the heap space).

2. Program storage distribution

        Regarding which data is stored in the Flash area and which data is stored in the SRAM area, this involves the storage state of the program. The application program has two states: static and running .

        The static program is stored in non-volatile memory, such as the internal FLASH area, so it can be saved normally even after the system is powered off.

        But when the program is running, the program often needs to modify some temporary data (such as initializing non-zero value data). These data are often stored in Flash, but because they need to be modified, these data need to be modified when the program is running. is copied to RAM.

        Therefore, the behavior of a program in memory is different when it is at rest and when it is running. You can refer to the following figure for display:

bed17834e3f6a718f6861e2182ad06f8.png

3. The program occupies the space of Flash and SRAM

        When a program is stored in the chip's internal FLASH (i.e. ROM area ), the space it occupies is the sum of Code + RO-data + RW-data. Therefore, if these contents are larger than the chip's FLASH space, the program cannot be saved normally. The chip's FLASH is up. When the program is executed , it needs to occupy the internal SRAM space (that is, the RAM area ), and the occupied space is the sum of RW-data + ZI-data.

Note : Why is RW still stored in Rom? Because all the data in RAM is lost after power failure. The data in RAM is reassigned every time it is powered on. These fixed values ​​are stored in Rom every time . Why? The ZI segment is not included because the ZI data are all 0, so there is no need to include it. As long as the area where the ZI data is located is cleared to zero before running the program. Including it would waste storage space.

Conclusion, for a program to run normally, the following two conditions must be met:

  • The program needs to be downloaded to the FLASH space of the chip. The minimum space of FLASH should be greater than the sum of Code + RO Data + RW Data;

  • When the program is running, the space used by the chip's internal RAM should be greater than the sum of RW Data + ZI Data;

Note: After the program is compiled, open the map file of the project. You can also see the total size of the ROM in the last section of the map file:

5a720b3f2b7d781460af1c66ec50f0b9.png

5. Reasons for dividing each paragraph (essence)

        When I was studying before, I only remembered what content was stored in each segment. I didn't know why it was necessary to divide it into different segments, and I didn't take the initiative to understand why it was divided in this way. Now we know how important it is to understand the divisions of these segments. Let’s analyze the basis for the division and what are the benefits of this division.

        First, you can distinguish between code segments and data segments. The machine instructions compiled from the program source code will be placed in the code segment; then the data segment here includes ".data", ".bss", ".rodata", and the global variables and local variables defined in the program are called first Called the data segment.

1. Why are the "code segment" and "data segment" of the program stored separately?

        When the program is loaded, data and instructions are mapped into two virtual memory areas respectively. The data segment is readable and writable for the process, while the code segment is read-only for the process. Therefore, the permissions of these two virtual memory areas can be set to read-write and read-only respectively to prevent the program instructions from being accessed. Rewriting both intentionally and unintentionally.

        The cache of modern CPUs is generally designed to separate the data cache and instruction cache. It is beneficial to improve the cache hit rate of the CPU by storing program instructions and data separately.

        When multiple copies of the program are running in the system, for example, multiple threads are running the same program at the same time, their code segment instructions are the same, so only one copy of the program's code segment needs to be saved in the memory, and then The data segment area of ​​each copy process is divided, which can save a lot of space.

2. Why is it so troublesome to divide data segments into .data, .bss, and .rodata? What's the difference?

        It is mainly distinguished based on two dimensions, whether it occupies memory space and read and write permissions.

        Initialized global variables and local static variables are saved in the ".data" section. Uninitialized global variables and local static variables are generally placed in the ".bss" section, because the default value of uninitialized variables is 0. Originally, they could also be placed in the .data section, but because they are all 0, they are not in the ".bss" section. It is not necessary to allocate space in the .data section and store data 0.

        The ".data" segment and the ".bss" segment are both readable and writable data segments, while ".rodata" stores read-only data, mainly some const variables and string constants. The advantage of setting up a separate ".radata" section is that the attributes of the ".rodata" section can be mapped to read-only when the program is loaded, so that any modification operation to this section will be treated as an illegal operation. In addition, on some platforms, the ".rodata" section can also be stored in read-only memory, such as ROM, which is guaranteed to be read-only through hardware.

3. Why are global variables still divided into initialized and uninitialized?

        Why are global variables stored separately? Global variables initialized to 0 and uninitialized are placed in the BSS area, and global variables initialized not to 0 are stored in the data area.

        There are two storage states in the program, one is the static state and the other is the running state. The program in the static state is stored in the non-volatile memory. The microcontroller is usually placed in the flash, so it can be saved normally after power failure;

        However, when the program is running, the program often needs to modify some temporary data. Due to the requirements of running speed, these data are often stored in memory (RAM) and will be lost when power is lost. At this time, we can initialize the uninitialized and initialized data as Global variables of 0 are placed in SRAM, because SRAM will automatically initialize the variables in it to 0; this can reduce the number of times of reading data from ROM and improve the efficiency of the entire program;

        From the figure below, we can see that when in the running state, SRAM will automatically initialize the variables placed in it to 0, SRAM will automatically initialize the data placed in it to 0, and then run the program to reset the data in the RW section. Extracted into SRAM:

b1b9b2d28d1e1e108f29e5f2fcab6423.png

        The left side of the picture above is the storage status of the application, the right side is the running status, the upper brown area is the RAM memory area, and the lower yellow area is the ROM memory area.

        When the program is in storage state, the RO section and RW section are saved in the ROM area (the data cannot be modified). When the program starts running, the kernel reads the code directly from the ROM, and before executing the application code, it will first execute a load code, which copies the RW segment data from ROM to RAM (because the RW data may be needed during execution modified), and add the ZI segment to the RAM, the data in the ZI segment will be initialized to 0. After loading, the RAM area is prepared and the main program officially begins to execute.

        The data of RW-data generated by compilation belongs to the RW end in the figure, and the data of ZI-data belongs to the ZI segment in the figure. Whether it needs to be saved after power off is the reason why RW-data and ZI-data are stored differently, because when RAM creates data, the default value is 0, but if some data requires an initial value other than 0, then ROM needs to be used. Record the initial value and copy it to RAM during runtime.

         This is the end of this issue. If you think the article is good, you can like, collect and follow it. Thank you everyone for watching. See you in the next issue!

        The above are all practical information, which will be encountered not only at work, but also in interviews. Therefore, mastering this part of the content is unavoidable for embedded technicians. It is recommended to collect and save it. If you find it helpful, you can tip Lao Wang. Your support is my biggest motivation to move forward.

        Okay, let me tell you something. I had no intention of setting up a WeChat group before, but a fan asked yesterday, and after listening to his suggestions, I decided to create a WeChat technology exchange group! Please follow the WeChat official account for details .


         For more knowledge about embedded C language, FreeRTOS, RT-Thread, Linux application programming, Linux driver and other related knowledge, follow the public account [Embedded Linux Knowledge Sharing], and watch the follow-up exciting content in time.

 

Guess you like

Origin blog.csdn.net/Wang_XB_3434/article/details/131318213