当前位置: 代码迷 >> 综合 >> Ubuntu系统、STM32下重温全局变量、局部变量、堆、栈。
  详细解决方案

Ubuntu系统、STM32下重温全局变量、局部变量、堆、栈。

热度:65   发布时间:2023-12-04 19:49:36.0

目录

一、知识点

1.1 全局变量 & 局部变量

1.2 堆 & 栈

1.3 stm32数据的存储位置

二、Ubuntu(x86)系统和STM32(Keil)中编程验证

2.1 代码编写

2.2 ubuntu运行

2.3 stm(keil)运行

2.3.1工程建立

2.3.2编译

2.3.2 运行结果

2.3.3 查看stm32地址的分配

三、总结


一、知识点

1.1 全局变量 & 局部变量

  • 全局变量

在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件。

  • 局部变量

定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数的内部就是无效的,再使用就会报错。

  • 二者之间的区别
全局变量 局部变量
定义位置 在方法外部,直接写在类中 在方法内部
作用范围 整个类中都可以使用 只能在方法中使用
默认值 如果没有赋值,则有默认值,规则同数组 没有默认值,要使用必须手动赋值
内存位置 位于堆内存 位于栈内存

1.2 堆 & 栈

  • STM32中的堆栈

单片机是一种集成电路芯片,集成CPU、RAM、ROM、多种I/O口和中断系统、定时器/计数器等功能。CPU中包括了各种总线电路,计算电路,逻辑电路,还有各种寄存器。

stm32 有通用寄存器 R0‐ R15 以及一些特殊功能寄存器,其中包括了堆栈指针寄存器。
当stm32正常运行程序的时候,来了一个中断,CPU就需要将寄存器中的值压栈到RAM里,然后将数据所在的地址存放在堆栈寄存器中。
等中断处理完成退出时,再将数据出栈到之前的寄存器中,这个在C语言里是自动完成的。

  • 程序的内存分配

一般程序占用的内存分为以下几个部分:

①栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

②堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。它与数据结构中的堆是两回事,分配方式类似于链表。

③全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后有系统释放

④文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放

⑤程序代码区—存放函数体的二进制代码。


正常的程序在内存中通常分为程序段、数据段、堆栈三部分。
程序段里放着程序的机器码、只读数据,这个段通常是只读,对它的写操作是非法的。
数据段放的是程序中的静态数据。
堆栈是内存中的一个连续的块。一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶。堆栈的底部是一个固定地址。堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。它支持两个操作,PUSH和POP。PUSH是将数据放到栈的顶端,POP是将栈顶的数据取出。动态数据存放在堆栈中。
 

内存图解:

1.3 stm32数据的存储位置


RAM(随机存取存储器)
存储的内容可通过指令随机读写访问。RAM中的存储的数据在掉电是会丢失,因而只能在开机运行时存储数据。其中RAM又可以分为两种,一种是Dynamic RAM(DRAM动态随机存储器),另一种是Static RAM(SRAM,静态随机存储器)。栈、堆、全局区(.bss段、.data段)都是存放在RAM中。
ROM(只读存储器)
只能从里面读出数据而不能任意写入数据。ROM与RAM相比,具有读写速度慢的缺点。但由于其具有掉电后数据可保持不变的优点,因此常用也存放一次性写入的程序和数据,比如主版的BIOS程序的芯片就是ROM存储器。代码区和常量区的内容是不允许被修改的,所以存放于ROM中。

二、Ubuntu(x86)系统和STM32(Keil)中编程验证

2.1 代码编写

#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] = "yaoyao";//栈//定义常量字符串char *var1 = "1234567890";char *var2 = "abcdefghij";//动态分配——堆区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;
}

2.2 ubuntu运行

  • 首先vim .c文件
  • 进行gcc编译生成.o文件
  • 运行可执行文件

 可以看到地址值从上到下逐步增大,而不同的区从上到下依次减小。

2.3 stm(keil)运行

2.3.1工程建立

  • cubemx建立串口程序

  • 修改main函数
#include "main.h"
#include "usart.h"
#include "gpio.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");
}/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes *//* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//*** @brief  The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 *//* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */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;/* USER CODE END 3 */
}
  •  重定向printf和scanf

参考博客:stm32cubemx下stm32中断与串口DMA通信_Laul Ken-Yi的博客-CSDN博客

记住要勾选微型库microlib

2.3.2编译

编译后,我们可以看到存在Code、RO-data、RW-data、ZI-data四个代码段大小

其中Code是代码占用大小,RO-data是只读常量、RW-data是已初始化的可读可写变量,ZI-data是未初始化的可读可写变量。

有些时候,我们需要知道RAM和ROM的使用情况如何,那么我们就可以使用下面的公式计算。

RAM = RW-data + ZI-data
ROM = Code + RO-data + RW-data
Flash=Code + RO Data + RW Data

这个是 MDK 编译之后能够得到的每个段的大小,也就能得到占用相应的FLASH和RAM的大小,但是还有两个数据段也会占用RAM,但是是在程序运行的时候,才会占用,那就是堆和栈。

2.3.2 运行结果


 通过运行结果可以发现,Ubuntu在栈区和堆区的地址值都是从上到下增长的,树莓派和stm32的栈区的地址值是从上到下减小的,堆区则是从上到下增长的。从每个区来看,地址值是从上到下逐步减小的,即栈区的地址是高地址,代码区的地址是处于低地址。

2.3.3 查看stm32地址的分配

从图片中可以看出ROM的地址分配是从0x8000000开始,整个大小为0x10000,这个部分用于存放代码区和文字常量区。RAM的地址分配是从0x20000000开始,其大小是0x5000,这个区域用来存放栈、堆、全局区(.bss段、.data段)。与代码结果显示进行对比,也可以看出对应得部分得地址与设置的是相对应的。


三、总结

        对c语言的内存分配有了更深的了解,知道了c程序的内存分配包括几个部分以及每个部分的作用,以及ROM和RAM寄存器的起始地址和分配空间的大小,对今后复杂程序的理解有很大的帮助。

参考文献:

基于ubuntu,树莓派和stm32的C程序的内存分配问题_Harriet的博客-CSDN博客

【嵌入式18】Ubuntu、stm32下的程序内存分配问题(堆栈、局部全局变量等)_噗噗的罐子博客-CSDN博客