Ubuntu、stm32下的C程序中内存分配问题

编写一个C程序,重温全局变量、局部变量、堆、栈等概念,在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证(STM32 通过串口printf 信息到上位机串口助手) 。归纳出Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析。

一、C程序的内存分配

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

相关图解如下
在这里插入图片描述

二、Ubuntu编程并验证

在Ubuntu中进入终端页面
输入:sudo nano text.c
创建一个文件text.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 text.c -o text,进行编译
输入 ./text 运行
在这里插入图片描述
可以看出栈区地址值从上到下逐渐增大
堆区地址值从上到下逐渐增大

三、在stm32中验证

1、新建工程

打开STM32CubeMX
点击File>New Project,新建一个工程
在这里插入图片描述
在右上角根据自己的芯片信号选择对应的选项,我这里选择的是STM32F 103C8,然后单击右下角的item选中,再单击右上角的Start Proj…
在这里插入图片描述
SYS一栏中将Debug勾选为Serial Wire
在这里插入图片描述
然后,进行时钟配置
Sysrem Core中选择RCC,将HSE那一栏更改为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
在这里插入图片描述
接着点击Connnectivity,选择USART1串口1,Mode选择Asynchronous
在这里插入图片描述
选择Project Manager一栏

  • 修改工程名称,我这里设置为test15
  • 修改工程路径,根据自己的情况
  • 修改编译器以及编译器的版本号,这里我修改为MDK-ARM V5.32

在这里插入图片描述
选择Code Generator一栏,选中Generate peripheral initialization...生成.c/.h文件
最后点击右上角GENERATE CODE 生成代码
在这里插入图片描述
在这里插入图片描述

2、编写函数

待生成成功后,点击Open Project,打开工程
由于要使用C语言标准库函数,所以需要一些配置
点击Options for Target
在这里插入图片描述
在菜单栏中选择Target,然后勾选use MicroLIB,D点击OK保存配置
在这里插入图片描述
(1) usart.c代码修改
首先,在usart.c文件中添加头文件#include<stdio.h>
在这里插入图片描述
添加以下代码在如图所示的位置,完成重定向

// 重定向函数
int fputc(int ch,FILE *f)
{
    uint8_t temp[1]={ch};
    HAL_UART_Transmit(&huart1,temp,1,2);        //UartHandle是串口的句柄
}

在这里插入图片描述
(2) main.c代码修改
在main.c文件中添加头文件

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

在这里插入图片描述

然后添加以下代码

#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");
}

在这里插入图片描述
在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);

在这里插入图片描述

3、编译烧录

编译文件
在这里插入图片描述
没有错误,并且生成了hex文件
在这里插入图片描述

打开mcuisp烧录软件

  • 搜索串口
  • 选择生成的hex文件
  • 点击读器件信息
  • 点击开始编程

出现右边的信息,说明烧录成功
在这里插入图片描述

4、运行结果

打开野火串口调试助手,点击打开串口
可以发现,stm32的栈区的地址值是从上到下减小的,堆区是从上到下增大的。
在这里插入图片描述

5、结果分析

(1)STM32区域分布
在一个STM32程序代码中,从内存高地址到内存低地址,依次分布着栈区、堆区、全局区(静态区)、常量区、代码区,其中全局区中高地址分布着.bss段,低地址分布着.data段。
分布如图所示
在这里插入图片描述

1、栈区(stack) 临时创建的局部变量存放在栈区。 函数调用时,其入口参数存放在栈区。 函数返回时,其返回值存放在栈区。
const定义的局部变量存放在栈区。 2、堆区(heap) 堆区用于存放程序运行中被动态分布的内存段,可增可减。
可以有malloc等函数实现动态分布内存。 有malloc函数分布的内存,必须用free进行内存释放,否则会造成内存泄漏。
3、全局区(静态区) 全局区有.bss段和.data段组成,可读可写。
4、.bss段 未初始化的全局变量存放在.bss段。
初始化为0的全局变量和初始化为0的静态变量存放在.bss段。 .bss段不占用可执行文件空间,其内容有操作系统初始化。
5、.data段
已经初始化的全局变量存放在.data段。 静态变量存放在.data段。 .data段占用可执行文件空间,其内容有程序初始化。
const定义的全局变量存放在.rodata段。 6、常量区 字符串存放在常量区。 常量区的内容不可以被修改。 7、代码区
程序执行代码存放在代码区。 字符串常量也有可能存放在代码区。

(2)stm32数据的存储位置
RAM(随机存取存储器)
存储的内容可通过指令随机读写访问。RAM中的存储的数据在掉电是会丢失,因而只能在开机运行时存储数据。其中RAM又可以分为两种,一种是Dynamic RAM(DRAM动态随机存储器),另一种是Static RAM(SRAM,静态随机存储器)。栈、堆、全局区(.bss段、.data段)都是存放在RAM中。
ROM(只读存储器)
只能从里面读出数据而不能任意写入数据。ROM与RAM相比,具有读写速度慢的缺点。但由于其具有掉电后数据可保持不变的优点,因此常用也存放一次性写入的程序和数据,比如主版的BIOS程序的芯片就是ROM存储器。代码区和常量区的内容是不允许被修改的,所以存放于ROM中。
(3)stm32地址分配
点击Options ,查看stm32地址的分配
在这里插入图片描述
在这里插入图片描述
可以看出,ROM的地址分配从0x8000000开始,大小为0x10000,该区域一般用于存放代码和文字产量
RAM的地址分配从0x20000000开始,大小为0x5000,该区域用于存放栈、堆、全局区(.bss段、data段)

四、总结

通过这次的实验,我对于C程序的内存分配有了更深的理解。通过比较Ubuntu和stm32中的不同,了解了不同系统中区域内的地址值变化的不同之处。对于我学习stm32有很大的帮助。

参考资料

https://my.oschina.net/mizhinian/blog/4472814
https://blog.csdn.net/qq_43279579/article/details/110308101

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值