一、C程序中的一些变量及内存分配
1、全局变量
在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。
例如:
int a, b; //全局变量
void func1(){
//TODO:
}
float x,y; //全局变量
int func2(){
//TODO:
}
int main(){
//TODO:
return 0;
}
a、b、x、y 都是在函数外部定义的全局变量。C语言代码是从前往后依次执行的,由于 x、y 定义在函数 func1() 之后,所以在 func1() 内无效;而 a、b 定义在源程序的开头,所以在 func1()、func2() 和 main() 内都有效。
2、局部变量
定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。例如:
int f1(int a){
int b,c; //a,b,c仅在函数f1()内有效
return a+b+c;
}
int main(){
int m,n; //m,n仅在函数main()内有效
return 0;
}
3、内存分配
C程序的内存分配如下:
栈区(stack):
由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区(heap):
一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。它与数据结构中的堆不同,分配方式类似于链表。
全局区(静态区)(static):
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量、未初始化的静态变量在相邻的
另一块区域。当程序结束后,变量由系统释放 。
文字常量区:
存放常量字符串。当程序结束后,常量字符串由系统释放 。
程序代码区:
存放函数体的二进制代码。
存储区可以用如下图片表示:
4、内存段
内存段可以分为:bss 段、data段、text段、堆(heap)和栈(stack)。
bss 段:
bss 段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域;
bss 是英文Block Started by Symbol的简称;
bss 段属于静态内存分配。
data 段:
数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域;
数据段属于静态内存分配。
text 段:
代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域;
这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序);
在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
堆(heap):
堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减;
当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);
当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
栈(stack):
栈又称堆栈,是用户存放程序临时创建的局部变量;也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量);除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中;由于栈的先进先出(FIFO)特点,所以栈特别方便用来保存/恢复调用现场;
从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
5、内存管理
STM32 的存储器结构中 Flash,SRAM 寄存器和输入输出端口被组织在同一个 4GB 的线性地址空间内。
可访问的存储器空间被分成8个主要块,每个块为512MB。
FLASH存储下载的程序,SRAM是存储运行程序中的数据。
所以,只要不外扩存储器,写完的程序中的所有东西也就会出现在这两个存储器中。
一、在Ubuntu和Keil中显示变量地址分配
1、Ubuntu中运行
通过一段程序,在程序中定义全局变量和局部变量,并通过程序输出这些变量所保存的地址信息。
#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] = "swj";//栈
//定义常量字符串
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在栈区和堆区的地址值都是逐步在增长的。
2、Keil中运行
代码如下:
#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] = "swj";
//定义常量字符串
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); //延时
}
}
运行结果:
观察可知,STM32的栈区地址逐渐减小,但是其堆区的地址是逐渐增大的。
总结
堆和栈空间分配
栈:向低地址扩展
堆:向高地址扩展
显然如果依次定义变量,先定义的栈变量的内存地址比后定义的栈变量的内存地址要大。先定义的堆变量的内存地址比后定义的堆变量的内存地址要小
堆和栈变量
栈:临时变量,退出该作用域就会自动释放。
堆:malloc变量,通过free函数释放。
堆和栈的区别:
stack的空间由操作系统自动分配/释放,heap上的空间手动分配/释放。
stack的空间有限,heap是很大的自由存储区。
程序在编译期和函数分配内存都是在栈上进行,且程序运行中函数调用时参数的传递也是在栈上进行。