当前位置: 代码迷 >> 综合 >> 基于ubuntu和stm32(keil5)的C程序的内存分配问题
  详细解决方案

基于ubuntu和stm32(keil5)的C程序的内存分配问题

热度:95   发布时间:2023-12-01 17:04:22.0

目录

  • 一、内存分区概念
    • 1.c语言在内存中分区
    • 2.动静态分配内存
    • 3.内存分区详解
  • 二、STM32上分区验证
    • 1.代码编写
    • 2.编译 烧录 输出结果
  • 三、在Ubuntu中进行分区验证
  • 四、关于分区的分析
  • 五、总结
  • 六、参考资料

一、内存分区概念

1.c语言在内存中分区

  • 栈区(stack)
    由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区(heap)
    一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。它与数据结构中的堆不同,分配方式类似于链表。
  • 全局区(静态区)(static)
    全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量、未初始化的静态变量在相邻的另一块区域。当程序结束后,变量由系统释放 。
  • 文字常量区
    存放常量字符串。当程序结束后,常量字符串由系统释放 。
  • 程序代码区
    存放函数体的二进制代码。

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

2.动静态分配内存

属性 描述
静态分配内存 在程序编译和链接时就确定好的内存
动态分配内存 在程序加载、调入、执行的时候分配 / 回收的内存

3.内存分区详解

属性 描述
.text 也称为代码段(Code),用来存放程序执行代码,同时也可能会包含一些常量(如一些字符串常量等)。该段内存为静态分配,只读(某些架构可能允许修改)。 这块内存是共享的,当有多个相同进程(Process)存在时,共用同一个 text 段
.data 也有的地方叫 GVAR (global value),用来存放程序中已经初始化的非零全局变量。静态分配。data又可分为读写(RW)区域和只读(RO)区域。RO 段保存常量所以也被称为 .constdata ;RW 段则是普通非常全局变量,静态变量就在其中
.bss 存放程序中为初始化的和零值全局变量。静态分配。在程序开始时通常会被清零

注:text 和 data 段都在可执行文件中,由系统从可执行文件中加载;而 bss 段不在可执行文件中,由系统初始化。 这三段内存就组成了我们编写的程序的本体,但是一个程序运行起来,还需要更多的数据和数据间的交互,否则这个程序就是死的,无用的。所以我们还需要为更多的数据和数据交互提供一块内存——堆栈。

堆和栈都属于动态分配内存,两者空间大小都是可变的。

属性 描述
Stack:栈 存放 Automatic Variables ,按内存地址由高到低方向生长,其最大值由编译时确定,速度快,但自由性差,最大空间不大
Heap:堆 自由申请的空间,按内存地址由低到高方向生长,其大小由系统内存 / 虚拟内存上限决定,速度较慢,但自由性大,可用空间大。
对比内容 全局/静态存储区 常量存储区
存储内容 局部变量 变量 全局变量、静态变量 常量
作用域 函数作用域、语句块作用域 函数作用域、语句块作用域
编译期间大小是否确定
大小 1MB 4GB
内存分配方式 地址由高向低减少 地址由低向高增加
内容是否可以修改

二、STM32上分区验证

说明:本部分内容基于stm32f103c8tx芯片采用中断方式向上位机(win10)发送“hello windows”———USART串口通讯进行改进

1.代码编写

在串口通信所建的工程上修改代码
bsp_usart.h 文件:
添加头文件

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

在这里插入图片描述
bsp_usart.c 文件:
添加一个 fputc函数

int fputc(int ch, FILE *f)
{
    USART_SendData(DEBUG_USARTx, (uint8_t)ch);while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);return (ch);
}

在这里插入图片描述
main.c 文件:
编写如下代码:

#include "stm32f10x.h"
#include "bsp_usart.h" //添加 bsp_usart.h 头文件int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;void output(int a)
{
    printf("hello");printf("%d",a);printf("\n");
}int main(void)
{
    	//定义局部变量int a=2;static int inits_local_c=2, uninits_local_c;int init_local_d = 1;char *p;char str[10] = "lyy";//定义常量字符串char *var1 = "1234567890";char *var2 = "qwertyuiop";//动态分配int *p1=malloc(4);int *p2=malloc(4);USART_Config();//串口初始化output(a);//释放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;
}

在这里插入图片描述

2.编译 烧录 输出结果

编译:
在这里插入图片描述
烧录
在这里插入图片描述
打开串口助手,打开串口后,按一下芯片上的RESET,显示输出结果:
在这里插入图片描述

三、在Ubuntu中进行分区验证

使用命令创建一个hello.c文件

gedit hello.c

在这里插入图片描述
在hello.c文件中输入代码

#include <stdio.h>
#include <stdlib.h>
//定义全局变量
int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;
void output(int a)
{
    printf("hello");printf("%d",a);printf("\n");
}int main( )
{
       //定义局部变量int a=2;static int inits_local_c=2, uninits_local_c;int init_local_d = 1;output(a);char *p;char str[10] = "lyy";//定义常量字符串char *var1 = "1234567890";char *var2 = "qwertyuiop";//动态分配int *p1=malloc(4);int *p2=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;
}
  • 输入完成后保存并关闭文件
  • 输入命令编译执行
gcc hello.c -o hello
./hello

在这里插入图片描述
输出结果
在这里插入图片描述

四、关于分区的分析

从stm32和Ubuntu都可以看出:从高地址到低地址分区情况
在这里插入图片描述

五、总结

该实验主要是对c程序内存分配的进一步认识,之前的了解基本都是基于一些代码表面上的一些简单的分析。内存中的主要部分包括程序段、数据段、堆栈三个部分。在不同系统下区域内的地址值变化是不相同的。

六、参考资料

https://blog.csdn.net/ssj925319/article/details/110727925?spm=1001.2014.3001.5501

https://blog.csdn.net/ssj925319/article/details/110727904
【IoT】STM32 内存分配详解