一、实验要求
1、 编写一个C程序,重温全局变量、局部变量、堆、栈等概念,在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证(STM32 通过串口printf 信息到上位机串口助手) 。归纳出Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析。
2、安装Clion2021,采用一套新的嵌入式软件开发工具包(替代Keil),完成stm32F103 点亮LED的程序。
二、重温全局变量、局部变量、堆、栈等概念
在一个STM32程序代码中,从内存高地址到内存低地址,依次分布着栈区、堆区、全局区(静态区)、常量区、代码区,其中全局区中高地址分布着.bss段,低地址分布着.data段。
1.栈区(stack)
(1)临时创建的局部变量存放在栈区。
(2)函数调用时,其入口参数存放在栈区。
(3)函数返回时,其返回值存放在栈区。
(4)const定义的局部变量存放在栈区。
2、堆区(heap)
(1)堆区用于存放程序运行中被动态分布的内存段,可增可减。
(2)可以有malloc等函数实现动态分布内存。
(3)有malloc函数分布的内存,必须用free进行内存释放,否则会造成内存泄漏。
3、全局区(静态区)
全局区有.bss段和.data段组成,可读可写。
4、.bss段
(1)未初始化的全局变量存放在.bss段。
(2)初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
(3).bss段不占用可执行文件空间,其内容有操作系统初始化。
5、.data段
(1)已经初始化的全局变量存放在.data段。
(2)静态变量存放在.data段。
(3).data段占用可执行文件空间,其内容有程序初始化。
(4)const定义的全局变量存放在.rodata段。
6、常量区
(1)字符串存放在常量区。
(2)常量区的内容不可以被修改。
7、代码区
(1)程序执行代码存放在代码区。
(2)字符串常量也有可能存放在代码区。
二、在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证
代码如下:
#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] = "gt";
//定义常量字符串
char *var1 = "1234567890";
char *var2 = "haohaoshahaizi";
//动态分配
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上运行:
2.stm32开发板上运行(代码需要添加串口初始化函数)
(1)通过stmcube创建工程框架
创建工程这里不介绍了,大家直接用下面的就好,直接修改main.c函数中的即可。
工程链接
链接:https://pan.baidu.com/s/1SWCUT1Hvw-hXiyj20IaX1A
提取码:x7ba
代码如下:
#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("hello");
printf("%d",a);
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[10] = "haohao";
//定义常量字符串
char *var1 = "1234567890";
char *var2 = "haohaoshahaizi";
//动态分配
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;
}
}
点击魔法棒,勾选C99 Mode
,
直接运行。
(2).烧录文件
烧录文件,然后打开野火进行显示。
打开串口助手
三、总结
本次实验让我重温了变量存储、堆和栈的概念,同时了解到:
1.堆和栈空间分配:栈 向低地址扩展;堆 向高地址扩展
2.如果依次定义变量,先定义的栈变量的内存地址比后定义的栈变量的内存地址要大;先定义的堆变量的内存地址比后定义的堆变量的内存地址要小。
3.栈 临时变量,退出该作用域就会自动释放;堆 malloc变量,通过free函数释放。