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

本文详细介绍了C语言内存分区的概念,包括栈、堆、全局/静态存储区和文字常量区,并通过STM32和Ubuntu环境下的代码验证了内存分配。在STM32上实现了串口通信发送数据,展示了不同内存区域的地址。在Ubuntu中,同样通过程序验证了内存分区。分析了内存分区的布局,总结了内存分配的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、内存分区概念

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:堆自由申请的空间,按内存地址由低到高方向生长,其大小由系统内存 / 虚拟内存上限决定,速度较慢,但自由性大,可用空间大。
对比内容全局/静态存储区常量存储区
存储内容局部变量变量全局变量、静态变量常量
作用域函数作用域、语句块作用域函数作用域、语句块作用域
编译期间大小是否确定
大小1MB4GB
内存分配方式地址由高向低减少地址由低向高增加
内容是否可以修改

二、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.youkuaiyun.com/ssj925319/article/details/110727925?spm=1001.2014.3001.5501

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

参考资源链接:[树莓派4B与STM32结合ROS开发机器人的全套资源](https://wenku.youkuaiyun.com/doc/1mzgunhr3m?utm_source=wenku_answer2doc_content) 为了在树莓派4B上成功开发基于ROS的STM32控制机器人,你需要按照以下步骤进行操作: 首先,确保你已经准备好了所有必要的硬件组件,包括树莓派4B、STM32开发板、LDS-01激光雷达、MPU6050运动跟踪设备、直流减速电机(带编码器)以及任何其他需要的外围设备。 接下来,开始软件准备阶段。在树莓派4B上安装Ubuntu 18.04操作系统,这可以通过Raspberry Pi Imager轻松完成。安装完成后,确保更新系统至最新状态并安装必要的依赖项,如ROS Melodic,以及为STM32编译所需的交叉编译工具链。 为了编程STM32,你需要在PC上安装KEIL5。虽然树莓派4B上不能直接安装KEIL5,但你可以通过远程桌面或SSH连接到一台配备了KEIL5的Windows或Linux机器,从而编写、编译调试STM32代码。此外,确保你的环境支持STM32的Firmware升级调试。 在Ubuntu 18.04上安装ROS Melodic其他必要的ROS包。这可以通过添加ROS的官方软件源执行安装脚本来完成。安装完成后,你可能需要创建一个ROS工作空间,并将其配置到你的.bashrc文件中,以便于使用ROS命令。 为了实现STM32与树莓派之间的通信,你可能需要配置串口或者使用I2C、SPI等通信协议。通过编写相应的ROS节点,你可以从树莓派向STM32发送控制命令,并接收STM32返回的状态信息。 最后,使用VSCODE作为源代码编辑器,编写管理你的ROS包STM32固件代码。PUTTY则可以帮助你远程连接到树莓派,进行文件传输远程操作。 这个过程涉及到多个技术领域,为了深入了解这些技术的细节实现方式,强烈推荐查阅《树莓派4B与STM32结合ROS开发机器人的全套资源》。这份资源将为你提供全面的源码、文档说明以及项目实战案例,帮助你理解应用树莓派、STM32以及ROS在机器人开发中的实际应用。 参考资源链接:[树莓派4B与STM32结合ROS开发机器人的全套资源](https://wenku.youkuaiyun.com/doc/1mzgunhr3m?utm_source=wenku_answer2doc_content)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浩923

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值