温习全局常量、全局变量、局部变量、静态变量、堆、栈
引言
文章目录
一、各种变量概念理解
1.常量
c++中常量修饰符为const,告诉编译器这个名字是常量。如果定义了某对象为常量,然后试图想修改它,编译器会报错的。
const int x=10;
注意:
1)在C中,编译器对待const如同变量一样,只不过带有一个特殊的标记,意思是:“不要改变我”。
2)在c++中,一个const常量必须有初始值。
2.全局变量
全局变量顾名思义就是在所有函数体(包括main函数)的外部定义的,程序的所有部分(甚至外部文件中的代码)都可以使用它的,这也就是说全局变量不受作用域的影响,总是可用的。
#include<iostream>
using namespace std;
int globe;//全局变量
static int satus;
void digui(int a)
{
if(a<10)
{
cout << a << ' ';
digui(a + 1);
}
}
void main04()
{
digui(2);
}
注意:
1)想在一个文件中使用另外一个文件定义的全局变量,可以用extern关键字,那么就可以在该文件使用了。
extern int globe
2)全局变量的生命周期一直到程序结束。
3.局部变量
局部变量出现在一个作用域内,它们是局限与一个函数的。因此常被称为自动变量,因为它们在进入作用域是自动生成,离开作用域时自动消失,常用关键字auto,但是局部变量默认为auto,所以没有必要声明auto。
4.静态变量
静态变量常用static关键字修饰,通常情况下,函数中定义的局部变量在函数作用域结束时消失。当再次调用这个函数时,再被创建,再被初始化,但是想局部变量的值在程序的整个生命周期都仍然存在,我们就把它定义为static静态变量,具有记忆的功能。
但是为啥不使用全局变量呢?static变量的优点在于在函数范围之外它是不可用的,所以不容易被修改。
static还可以作用于函数和外部变量,它的意思是“在文件的外部不可以使用这个名字”。函数名或者变量名是局限于文件的
5.外部变量
外部变量常用关键字为:extern,他告诉编译器存在一个变量和函数,即使在当前编译的文件中没看见他,但是可能存在当前文件的后面或者另外的一个文件。
6.内存分区
程序运行前 :
代码区(存放CPU执行的机器指令):
共享的,共享的目的是对于频繁被执行的程序,只需在内存中存放一份代码即可
只读的,使其只读的原因是防止程序以外地被修改了它的指令。
全局区(存放全局变量、静态变量、字符串常量、全局常量):
该区域的数据在程序结束后由操作系统释放。
程序运行后:
栈区(局部变量、形参):
由编译器自动分配和释放,存放函数的参数、局部变量等
注意:不要反悔局部变量的地址
堆区:
由程序员分配释放,如果程序员不释放,程序结束时由系统回收
c++中利用new开辟内存,返回堆上的地址,用指针接收
二、变量存储区域介绍
- C语言在内存中一共分为如下几个区域
区域 | 作用 |
---|---|
内存栈区 | 存放局部变量名 |
内存堆区 | 存放new或者malloc出来的对象 |
常数区 | 存放局部变量或者全局变量的值 |
静态区 | 用于存放全局变量或者静态变量 |
代码区 | 二进制代码 |
内存相关的详细介绍
-
栈区(stack)–由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
-
堆区(heap)–般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。
-
全局区(静态区)(static)–全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域(RW),未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(ZI)。程序结束后有系统释放。 和“栈”一样,通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。
-
文字常量区–常量字符串就是放在这里的。程序结束后由系统释放 (RO)
-
程序代码区–存放函数体的二进制代码。 (RO)
-
常量存储区—和“全局/静态存储区”一样,通常是用于那些在编译期间就能确定存储大小的常量的存储区,并且在程序运行期间,存储区内的常量是全局可见的。这是一块比较特殊的存储去,他们里面存放的是常量,不允许被修改。
-
内存存在两种属性:静态分配内存和动态分配内存。
静态分配内存:是在程序编译和链接时就确定好的内存。
动态分配内存:是在程序加载、调入、执行的时候分配/回收的内存。 -
Stack: 栈,存放Automatic Variables,按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。通常是用于那些在编译期间就能确定存储大小的变量的存储区,用于在函数作用域内创建,在离开作用域后自动销毁的变量的存储区。通常是局部变量,函数参数等的存储区。他的存储空间是连续的,两个紧密挨着定义的局部变量,他们的存储空间也是紧挨着的。栈的大小是有限的,通常Visual C++编译器的默认栈的大小为1MB,所以不要定义int a[1000000]这样的超大数组。
-
Heap: 堆,自由申请的空间,按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。
通常是用于那些在编译期间不能确定存储大小的变量的存储区,它的存储空间是不连续的,一般由malloc(或new)函数来分配内存块,并且需要用free(delete)函数释放内存。如果程序员没有释放掉,那么就会出现常说的内存泄漏问题。需要注意的是,两个紧挨着定义的指针变量,所指向的malloc出来的两块内存并不一定的是紧挨着的,所以会产生内存碎片。另外需要注意的一点是,堆的大小几乎不受限制,理论上每个程序最大可达4GB。
每个线程都会有自己的栈,但是堆空间是共用的。 -
Text & Data & Bss
.text: 也称为代码段(Code),用来存放程序执行代码,同时也可能会包含一些常量(如一些字符串常量等)。该段内存为静态分配,只读(某些架构可能允许修改)。 这块内存是共享的,当有多个相同进程(Process)存在时,共用同一个text段。
.data: 也有的地方叫GVAR(global value),用来存放程序中已经初始化的非零全局变量。静态分配。 data又可分为读写(RW)区域和只读(RO)区域。
-> RO段保存常量所以也被称为.constdata
-> RW段则是普通非常全局变量,静态变量就在其中 .bss: 存放程序中为初始化的和零值全局变量。静态分配,在程序开始时通常会被清零。
接下来通过一张详细的图解来说明,具体的位置
三、在Ubuntu(x86)系统中输出信息进行验证
我们编写如下的代码,在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] = "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;
}
得到如下的结果:
分析结果
栈存放区域是由高地址到低地址向下增长
堆存放区是由低地址到高地址像上增长
静态变量地址从高地址到低地址向下增长
函数地址是从低地址到高地址向上增长
四、在Keil中针对stm32系统进行验证
在Keil中编写如下代码:
#include "Serial.h"
#include <stdio.h>
#include <stdlib.h>
#include "stm32f10x.h" // Device header
//定义全局变量
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;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
Serial_Init();
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] = "Lx567";
//定义常量字符串
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;
}
}
编译成功
展示结果:
结果:stm32的栈区的地址值是从上到下减小的,堆区则是从上到下增长的
大佬友情链接
- https://blog.csdn.net/qq_46467126/article/details/121875496
- https://blog.csdn.net/qq_42762607/article/details/110391819
- https://blog.csdn.net/He3he3he/article/details/84592000