C语言程序里全局变量、局部变量、堆、栈等概念
C语言程序里全局变量、局部变量、堆、栈等概念
一、 存储区域的介绍
1.1 内存分配
- 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
- 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
相关介绍来源:C语言程序的内存分配方式
1.2 变量的内存分配
栈区(stack):指那些由编译器在需要的时候分配,不需要时自动清除的变量所在的储存区,如函数执行时,函数的形参以及函数内的局部变量分配在栈区,函数运行结束后,形参和局部变量去栈(自动释放)。栈内存分配运算内置与处理器的指令集中,效率高但是分配的内存空间有限。
堆区(heap):指哪些由程序员手动分配释放的储存区,如果程序员不释放这块内存,内存将一直被占用,直到程序运行结束由系统自动收回,c语言中使用malloc,free申请和释放空间。
静态储存区(static):全局变量和静态变量的储存是放在一块的,其中初始化的全局变量和静态变量在一个区域,这块空间当程序运行结束后由系统释放。
常量储存区(const):常量字符串就是储存在这里的,如“ABC”字符串就储存在常量区,储存在常量区的只读不可写。const修饰的全局变量也储存在常量区,const修饰的局部变量依然在栈上。
程序代码区:存放源程序的二进制代码。
引自:C语言:内存分配
1.3 图解
图片来源:sw-at ,需要*才可以访问。
补充:
- Stack: 栈,存放Automatic Variables,按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但*性差,最大空间不大。
- Heap: 堆,*申请的空间,按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但*性大,可用空间大。
每个线程都会有自己的栈,但是堆空间是共用的。
我们以下将按着图解以及补充信息进行相关编程验证。
二、 在ubuntu系统中编程并进行验证
2.1 源码
- main.c
#include <stdio.h>
#include <stdlib.h>
int k1 = 1; //已初始化全局int型变量k1
int k2; //未初始化全局int型变量k2
static int k3 = 2; //已初始化静态全局int型变量k3
static int k4; //未初始化静态全局int型变量k4
int test()
{
int j1;
int j2;
printf("未初始化局部int型变量j1 :%p\n", &j1);
printf("未初始化局部int型变量j2 :%p\n", &j2);
return 0;
}
int main()
{
static int m1 = 2; //已初始化静态局部int型变量m1
static int m2; //未初始化静态局部int型变量m2
int i1; //未初始化局部int型变量i1
int i2; //未初始化局部int型变量i2
char *p; //未初始化局部char型指针变量p
char str[10] = "hello"; //已初始化局部char型数组str
char *var1 = "123456"; //已初始化局部char型指针变量var1
char *var2 = "abcdef"; //已初始化局部char型指针变量var2
int *p1 = malloc(4); //已初始化局部int型指针变量p1
int *p2 = malloc(4); //已初始化局部int型指针变量p2
printf("栈区-变量地址\n");
printf("未初始化局部int型变量i :%p\n", &i1);
printf("未初始化局部int型变量i2 :%p\n", &i2);
printf("未初始化局部char型指针变量p :%p\n", &p);
printf("已初始化局部char型数组str :%p\n", str);
test();
printf("\n堆区-动态申请地址\n");
printf("已初始化局部int型指针变量p1 :%p\n", p1);
printf("已初始化局部int型指针变量p2 :%p\n", p2);
printf("\n.bss段地址\n");
printf("未初始化全局int型变量 k2 :%p\n", &k2);
printf("未初始化静态全局int型变量k4 :%p\n", &k4);
printf("未初始化静态局部int型变量m2 :%p\n", &m2);
printf("\n.data段地址\n");
printf("已初始化全局int型变量k1 :%p\n", &k1);
printf("已初始化静态全局int型变量k3 :%p\n", &k3);
printf("已初始化静态局部int型变量m1 :%p\n", &m1);
printf("\n常量区地址\n");
printf("已初始化局部char型指针变量var1:%p\n", var1);
printf("已初始化局部char型指针变量var2:%p\n", var2);
printf("\n代码区地址\n");
printf("程序代码区main函数入口地址 :%p\n", &main);
free(p1);
free(p2);
return 0;
}
2.2 结果
2.3 结果分析
查看相关资料解释说在一个函数中难以查看栈的地址分配高低,所以这里用了两个函数进行了验证。
可以从上图可以得出栈区内存地址由高到低方向生长,堆区内存地址由低到高方向生长。而且整个程序的内存也是从高到低的地址进行分配的。
三、 在Keil中针对stm32系统进行编程进行验证
3.1 详细代码
#include "led.h"
#include "delay.h"
//#include "key.h"
#include "sys.h"
#include "usart.h"
#include <stdio.h>
#include <stdlib.h>
int k1 = 1; //已初始化全局int型变量k1
int k2; //未初始化全局int型变量k2
static int k3 = 2; //已初始化静态全局int型变量k3
static int k4; //未初始化静态全局int型变量k4
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
//KEY_Init(); //初始化与按键连接的硬件接口
while(1)
{
static int m1 = 2; //已初始化静态局部int型变量m1
static int m2; //未初始化静态局部int型变量m2
int i1; //未初始化局部int型变量i1
int i2; //未初始化局部int型变量i2
char *p; //未初始化局部char型指针变量p
char str[10] = "hello"; //已初始化局部char型数组str
char *var1 = "123456"; //已初始化局部char型指针变量var1
char *var2 = "abcdef"; //已初始化局部char型指针变量var2
int *p1 = malloc(4); //已初始化局部int型指针变量p1
int *p2 = malloc(4); //已初始化局部int型指针变量p2
printf("栈区-变量地址\r\n");
printf("未初始化局部int型变量i1 :0x%p\r\n", &i1);
printf("未初始化局部int型变量i2 :0x%p\r\n", &i2);
printf("未初始化局部char型指针变量p :0x%p\r\n", &p);
printf("已初始化局部char型数组str :0x%p\r\n", str);
//test();
printf("\n堆区-动态申请地址\r\n");
printf("已初始化局部int型指针变量p1 :0x%p\r\n", p1);
printf("已初始化局部int型指针变量p2 :0x%p\r\n", p2);
printf("\n.bss段地址\r\n");
printf("未初始化全局int型变量k2 :0x%p\r\n", &k2);
printf("未初始化静态全局int型变量k4 :0x%p\r\n", &k4);
printf("未初始化静态局部int型变量m2 :0x%p\r\n", &m2);
printf("\n.data段地址\r\n");
printf("已初始化全局int型变量k1 :0x%p\r\n", &k1);
printf("已初始化静态全局int型变量k3 :0x%p\r\n", &k3);
printf("已初始化静态局部int型变量m1 :0x%p\r\n", &m1);
printf("\n常量区地址\r\n");
printf("已初始化局部char型指针变量var1:0x%p\r\n", var1);
printf("已初始化局部char型指针变量var2:0x%p\r\n", var2);
printf("\n代码区地址\r\n");
printf("程序代码区main函数入口地址 :0x%p\r\n", &main);
free(p1);
free(p2);
}
}
3.2 结果
其中Code就是代码占用大小,RO-data是只读常量、RW-data是已初始化的可读可写变量,ZI-data是未初始化的可读可写变量。
串口显示XCOM中出现乱码,通过分析和检查之后发现是软件版本过低,导致快速输出串口信息时出现乱码现象。但并不影响分析。
3.3结果分析
四、 总结
通过对C语言程序里全局变量、局部变量、堆、栈等概念的重温以及在不同平台进行编程验证,熟悉掌握了C语言中相关概念,并对整体的内存地址分配由高到低,以及栈区内存地址由高到低方向生长,堆区内存地址由低到高方向生长进行了验证。同时也非常感谢身边老师同学对我的帮助。