Ubuntu与STM32C程序中变量的地址分配

本文深入探讨了C程序的内存分配,包括堆、栈、全局区和局部区等,并通过实例在Ubuntu和STM32上验证变量地址分配。实验结果显示Ubuntu的栈和堆地址递增,而STM32的栈地址递减,堆地址递增。分析了STM32的RAM和ROM存储结构,强调了不同系统中内存分配的差异。
摘要由CSDN通过智能技术生成

一、前言

本文我将会和大家一起先回顾C程序中的全局变量局部变量的概念,并通过C程序分别在Ubuntu和STM32中找到变量的地址分配,并对其进行分析。

二、了解C程序的内存分配

1、堆区是程序里动态分配的内容,堆区的内存容量大,使用灵活,分配后要自行回收容易产生内存碎片。
2、栈区主要是存储函数的局部变量,然后程序结束后操作系统自行回收但是栈区容量较小。
3、全局区(静态区)(static),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域(.data),未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(.bss)。 - 程序结束后由系统释放。
4、文字常量区 —常量字符串就是放在这里的(.rodata)。 程序结束后由系统释放。
5、程序代码区—存放函数体的二进制代码(.text)。

按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。
按作用域分,全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。
全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。
详细请参考:全局变量和局部变量在内存里的区别

其实在程序运行时,由于内存的管理方式是以页为单位的,而且程序使用的地址都是虚拟地址,当程序要使用内存时,操作系统再把虚拟地址映射到真实的物理内存的地址上。所以在程序中,以虚拟地址来看,数据或代码是一块块地存在于内存中的,通常我们称其为一个段。而且代码和数据是分开存放的,即不储存于同于一个段中,而且各种数据也是分开存放在不同的段中的。

正常的程序在内存中可以分为程序段数据段堆栈三部分。
其中程序段里存放的是程序的机器码和只读数据,这个存储区是只读的,对于其进行写操作是非法的。
数据段中保存的是程序中的静态数据。
堆栈是一个地址连续的存储区。堆栈指针寄存器(SP)指向堆栈的栈顶,栈底的内存是固定的,存储的新数据作为栈顶,一个一个叠加起来,一直到出栈的时候栈顶的数据先出来从上至下,所以堆栈是后进先出。
内存区示意图:
在这里插入图片描述
相信现在大家都对我们的内存分配和变量存储地址有了一定的了解。

三、分别在Ubuntu和STM32中验证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;
}

1、在Ubuntu上运行:
在这里插入图片描述

请添加图片描述

通过观察上两图,我们可以发现在Ubuntu栈区地址是逐步增大的,同样堆区的地址也是逐渐增大的。

在STM32上运行:

由于在STM32上运行时需要我们将地址返回到上位机上,所以需要配合串口初始化函数才能在上位机上显示对应的地址信息。串口通信可以参考我之前的博客:STM32USART串口通信

我们只需要将main.c文件修改一下即可:


#include "stm32f10x.h" //STM32头文件
#include "sys.h"
#include "delay.h"

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



int main (void){//主程序
	u8 a=7,b=8;
	//初始化程序
	RCC_Configuration(); //时钟设置

	USART1_Init(115200); //串口初始化(参数是波特率)

	//主循环
	while(1){	
		int a=2;
	static int inits_local_c=2, uninits_local_c;
    int init_local_d = 1;
    output(a);
    char *p;
    char str[10] = "zzq";
    //定义常量字符串
    char *var1 = "1234567890";
    char *var2 = "abcdefghij";
    //动态分配
    int *p1=malloc(4);
    int *p2=malloc(4);
    //释放
    free(p1);
    free(p2);
    printf("栈区-变量地址\n\r");
    printf("                a:%p\n\r", &a);
    printf("                init_local_d:%p\n\r", &init_local_d);
    printf("                p:%p\n\r", &p);
    printf("              str:%p\n\r", str);
    printf("\n堆区-动态申请地址\n\r");
    printf("                   %p\n\r", p1);
    printf("                   %p\n\r", p2);
    printf("\n全局区-全局变量和静态变量\n\r");
    printf("\n.bss段\n");
    printf("全局外部无初值 uninit_global_a:%p\n\r", &uninit_global_a);
    printf("静态外部无初值 uninits_global_b:%p\n\r", &uninits_global_b);
    printf("静态内部无初值 uninits_local_c:%p\n\r", &uninits_local_c);
    printf("\n.data段\n\r");
    printf("全局外部有初值 init_global_a:%p\n\r", &init_global_a);
    printf("静态外部有初值 inits_global_b:%p\n\r", &inits_global_b);
    printf("静态内部有初值 inits_local_c:%p\n\r", &inits_local_c);
    printf("\n文字常量区\n\r");
    printf("文字常量地址     :%p\n\r",var1);
    printf("文字常量地址     :%p\n\r",var2);
    printf("\n代码区\n\r");
    printf("程序区地址       :%p\n\r",&main);
    printf("函数地址         :%p\n\r",&output);


        delay_ms(1000); //延时
		
	}
}



编译一下,竟然报错了:
在这里插入图片描述

error: #268: declaration may not appear after executable statement in block 通过在网上查找资料,我发现这个错误是因为声明不能出现在可执行状态之后,C语言关于变量的定义只能放在函数的开头,放在执行语句的前面定义,这是C89的标准。
后来的C99标准就已经改变了,无论定义在之前还是之后都是可以的。

解决方案:
点击“仙女棒”,在C/C++ 选项卡下的 C99 Mode勾选上即可:
在这里插入图片描述
再次编译,没有报错,完美!
在这里插入图片描述
接下来只需要我们将程序烧录进STM32并打开串口助手就OK啦!
我们一起来看看输出的结果吧:
在这里插入图片描述
如图我们可以发现STM32的栈区地址逐渐减小,但是其堆区的地址是逐渐增大的

四、结果分析

通过实验我们发现,Ubuntu在栈区和堆区的地址都是从上到下增长的,但是STM32栈区的地址是从上到下减小,而堆区是从上到下增长的。从每个区来看,栈区的地址都是高地址,代码区的地址都处于低地址。
STM32的数据存储位置:
RAM(随机存取存储器)
存储的内容可通过指令随机读写访问。RAM中的存储的数据在掉电是会丢失,因而只能在开机运行时存储数据。其中RAM又可以分为两种,一种是Dynamic RAM(DRAM动态随机存储器),另一种是Static RAM(SRAM,静态随机存储器)。栈、堆、全局区(.bss段、.data段)都是存放在RAM中。
ROM(只读存储器)
只能从里面读出数据而不能任意写入数据。ROM与RAM相比,具有读写速度慢的缺点。但由于其具有掉电后数据可保持不变的优点,因此常用也存放一次性写入的程序和数据,比如主版的BIOS程序的芯片就是ROM存储器。代码区和常量区的内容是不允许被修改的,所以存放于ROM中。
我们可以通过Keil来看出STM32的RAM和ROM存储器大小和起始地址值:
在这里插入图片描述

如图所示为我们单片机的RAM和ROM存储器的相关信息,其中RAM用于存放栈、堆和全局区(.bss段和.data段)。ROM用于存放代码区和文字常量区。

五、总结

主要是对C程序的内存分配有进一步的认识,知道一个C程序内存应该包括哪些部分。其中,主要是程序段、数据段、堆栈三个部分。不同系统下面,区域内的地址值变化是不相同。总的来说,是对内存的分配有了比较新的认识。

参考资料

全局变量和局部变量在内存里的区别
基于ubuntu,树莓派和stm32的C程序的内存分配问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值