目录
一、全局变量和局部变量
全局变量
在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件。
局部变量
定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数其定义将被无视,在函数外使用将会报错。
两者之间的区别
作用域:
全局变量:全局变量在程序的任何地方都可以被访问,即其作用域为整个程序。它们通常在程序顶部定义,在整个程序执行期间都存在。
局部变量:局部变量只能在其所在的特定范围内被访问。例如,在函数内部定义的局部变量只能在该函数中使用,它们的作用域仅限于函数内部。
可访问性:
全局变量:全局变量在整个程序中都可以被访问,包括在函数内部。函数可以直接访问全局变量,无需任何额外声明。
局部变量:局部变量只能在其定义的作用域内被访问。其他函数或全局作用域无法直接访问局部变量。
生命周期:
全局变量:全局变量在程序开始执行时创建,直到程序结束才会被销毁。它们的生命周期与整个程序的执行周期相同。
局部变量:局部变量在其所在的作用域被创建时分配内存,并在作用域结束时自动销毁。每当函数执行时,局部变量都会被创建和销毁。
冲突风险:
全局变量:由于全局变量的可访问性较高,可能会被多个函数同时访问和修改。这可能导致变量的非预期更改和值冲突。
局部变量:局部变量只在其定义的作用域内有效,不会与其他函数或全局变量发生冲突。这增加了代码的可维护性和可靠性。
内存位置
全局变量在C中的内存位置为堆区,而局部变量位于栈区
二.堆&栈
1、STM32中的堆栈
单片机是一种集成电路芯片,集成CPU、RAM、ROM、多种I/O口和中断系统、定时器/计数器等功能。CPU中包括了各种总线电路,计算电路,逻辑电路,还有各种寄存器。
stm32 有通用寄存器 R0‐ R15 以及一些特殊功能寄存器,其中包括了堆栈指针寄存器。
当stm32正常运行程序的时候,来了一个中断,CPU就需要将寄存器中的值压栈到RAM里,然后将数据所在的地址存放在堆栈寄存器中。
等中断处理完成退出时,再将数据出栈到之前的寄存器中,这个在C语言里是自动完成的。
2、程序的内存分配
一般程序占用的内存分为以下几个部分:
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。它与数据结构中的堆是两回事,分配方式类似于链表。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后有系统释放
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
//main.cpp
int a = 0; //全局初始化区
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main() {
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c = 0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
//分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
在一个STM32程序代码中,从内存高地址到内存低地址,依次分布着栈区、堆区、全局区(静态区)、常量去、代码区,其中全局区中高地址分布着.bss段,低地址分布着.data段。
通过下面的思维导图对这部分内容进行详细的介绍:
#include <stdio.h>
static unsigned int val1 = 1; //val1存放在.data段
unsigned int val2 = 1; //初始化的全局变量存放在.data段
unsigned int val3 ; //未初始化的全局变量存放在.bss段
const unsigned int val4 = 1; //val4存放在.rodata(只读数据段)
unsigned char Demo(unsigned int num) // num 存放在栈区
{
char var = "123456"; // var存放在栈区,"123456"存放在常量区
unsigned int num1 = 1 ; // num1存放在栈区
static unsigned int num2 = 0; // num2存放在.data段
const unsigned int num3 = 7; //num3存放在栈区
void *p;
p = malloc(8); //p存放在堆区
free(p);
return 1;
}
void main()
{
unsigned int num = 0 ;
num = Demo(num); //Demo()函数的返回值存放在栈区。
}
三.利用程序进行验证
代码
#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[20] = "constellation";//栈
//定义常量字符串
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;
}
ubuntu上实现
不难看出,在Ubuntu中,除.bss段,其余所有区的内存分配都是从上向下不断增长的。
在keil中验证
代码
#include "sys.h"
#include "usart.h"
#include "delay.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("the result is:");
printf("\n");
}
int main(void)
{
u16 t; u16 len; u16 times=0;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,115200); //串口初始化为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[20] = "constellation";
//定义常量字符串
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);
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3FFF;//得到此次接收到的数据长度
for(t=0;t<len;t++)
{
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//等待发送结束
}
USART_RX_STA=0;
}else
{
times++;
delay_ms(100);
}
}
return 0;
}
通过运行结果可以发现,Ubuntu在栈区和堆区的地址值都是从上到下增长的,stm32的栈区的地址值是从上到下减小的,堆区则是从上到下增长的。从每个区来看,地址值是从上到下逐步减小的,即栈区的地址是高地址,代码区的地址是处于低地址。
四.总结
对C程序的内存分配有进一步的认识,知道一个C程序内存应该包括哪些部分,然后了解了更多关于stm32的串口问题
参考文献:【嵌入式18】Ubuntu、stm32下的程序内存分配问题(堆栈、局部全局变量等)_ubuntu软件使用内存位置_噗噗的罐子的博客-CSDN博客