C\C++各种变量存放区域(代码、数据、堆、栈)
文章目录
变量&数据
int val=0;//val:数据,存放在数据区。在执行exe文件时直接加载,并不会产生机器指令:mov [val],0
//编译器编译的过程中,直接把全局量放在exe文件中,当exe文件被调用时就加载全局变量。
//全局对象,全局对象的构造函数先于主函数运行(?)
//全局变量与全局对象的区别?————《程序员的自我修养》
int g_max;//bss区(未初始化区)
int fun()
{
int x=10;//在函数调用时,会产生相关机器指令,所以称x为变量
//mov[x],0ah;
return 0;
}
变量&数据存放区域
一般情况下,一个可执行C程序在内存中主要包含5个区域,分别是代码段(text),数据段(data),BSS段,堆段(heap)和栈段(stack)。其中包含data段和bss段的整个区段被合称为数据区。
其中前三个段(text,data,bss)是程序编译完成就存在的,此时程序并未载入内存进行执行。后两个段(heap,stack)是程序被加载到内存中时,才存在的。
C++程序在执行时,将内存大方向划分为4个区域:
-
代码区(text):存放函数体的二进制代码(C程序编译后的机器指令,汇编代码),由操作系统进行管理。代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。
-
数据区(data):存放全局变量和静态变量以及常量。
-
数据区(bbs):存储未初始化的全局变量或者静态(全局)变量。编译器给处理成0。
-
栈区:静态内存分配,存放函数调用相关的参数、局部变量的值,以及在任务切换的上下文信息。栈区是由操作系统分配和管理的区域。
在Windows操作系统中,栈区的大小默认为1MB,但可以通过编译器或操作系统的设置来改变。在Linux系统中,栈区的大小默认大小为8MB,但可以通过ulimit命令来修改。
-
堆区:动态内存分配,由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。(malloc&free,new&delete)
内存四区意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
![img](https://gitee.com/zhengzhivon/images/raw/master/imgs/20210305135407422.png)
变量存放区域:
生命周期 | 作用域 | 默认值 | 内存区域 | 链接属性 | |
---|---|---|---|---|---|
静态全局变量 | 程序运行时创建,程序退出时销毁 | 整个文件 | 0 | 全局(静态)变量区 | 内部的 |
普通全局变量 | 程序运行时创建,程序退出时销毁 | 整个文件 | 0 | 全局(静态)变量区 | 外部的 |
静态局部变量 | 进入函数时创建,程序退出时销毁 | 函数内部 | 0 | 全局(静态)变量区 | 无 |
普通局部变量 | 进入函数时创建,函数退出时销毁 | 函数内部 | 随机值 | 栈 | 无 |
动态内存 | 调用创建函数创建,调用释放函数销毁 | 只要能获取指针都能用 | 随机值 | 堆 | 无 |
静态函数 | 无 | 整个文件 | 无 | 代码区 | 内部的 |
普通函数 | 无 | 整个文件 | 无 | 代码区 | 外部的 |
练习:请说明下面的指针分别指向什么位置
void foo(char a)
{
static char nCount;
char p1[] = “AAA”;
char *p2 = “AAA”;
char *p3 = (char *)malloc(10);
char *p4 = &a;
char *p5 = &nCount;
}
void (*p)(char) = foo;
&p:在数据段(全局变量区) p:在代码段
&p1:在栈区 p1 :在栈区
&p2 :在栈区
p3:在堆区
p4:在栈区
p5:在数据段(全局变量区)
BSS(Block Started by Symbol)区
程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而data和bss段属于程序数据。
为什么把程序的指令和数据的存放分开?
- 设置不同权限:当程序被装载后,数据和指令分别被映射到两个虚存区域。 由于数据区域对于进程来说是可读写的,而指令区域是只读的,所以这两个虚存区域的权限可以被分别设置成可读写和只读。这样可以防止程序的指令被有意或无意地改写。
- 指令区和数据区的分离有利于提高程序的局部性,提高缓存命中率:指令区的访问模式是顺序访问,访问频率较高;数据区包含程序中的数据,这些数据的访问模式和访问频率与指令不同,通常是随机访问,访问频率较低。将数据区与指令区分离可以使数据区的访问不会影响指令区的缓存命中率,从而减少缓存的替换次数,提高程序的执行效率。
- 指令共享:当系统中运行着多个该程序的副本时,它们的指令都是一样的,所以内存中只须要保存一份该程序的指令部分。对于指令这种只读的区域来说是这样,对于其他的只读数据也一样,比如很多程序里面带有的图标、图片、文本等资源也是属于可以共享的。不过然每个副本进程的数据区域是不一样的,它们是进程私有的。
程序运行
程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:代码区和数据区(data+bbs)。
数据区:全局变量、静态变量(static)、常量(字符串常量、全局常量)。
#include <iostream>
using namespace std;
//创建全局变量
int g_a = 10;//global_a
int g_b = 10;
//创建全局常量
const int c_g_a = 10;//const_global_a
const int c_g_b = 10;
int main()
{
//全局区
//全局变量、静态变量、常量(全局常量、字符串常量)
//创建普通局部变量
int a = 10;
int b = 10;
cout << "局部变量a的地址为:" << (int)&a << endl;
cout << "局部变量b的地址为:" << (int)&b << endl << endl;
cout << "全局变量g_a的地址为:" << (int)&g_a << endl;
cout << "全局变量g_b的地址为:" << (int)&g_b << endl << endl;
//创建静态变量
static int s_a = 10;//static_a
static int s_b = 10;
cout << "静态变量s_a的地址为:" << (int)&s_a << endl;
cout << "静态变量s_b的地址为:" << (int)&s_b << endl << endl;
//创建常量:字符串常量、const 修饰的变量
//字符串常量
cout << "字符串常量的地址为:" << (int)&"hello world" << endl << endl;
//const 修饰的变量
//const 修饰的全局变量
cout << "全局常量c_g_a的地址为:" << (int)&c_g_a << endl;
cout << "全局常量c_g_b的地址为:" << (int)&c_g_b << endl << endl;
//const 修饰的局部变量
const int c_l_a = 10;//const_local_a
const int c_l_b = 10;
cout << "局部常量c_l_a的地址为:" << (int)&c_l_a << endl;
cout << "局部常量c_l_b的地址为:" << (int)&c_l_b << endl << endl;
cout << endl << endl << endl;
system("pause");
}
程序运行后
堆区与栈区:
栈区
由编译器自动分配释放,存放函数的参数值(函数传参)以及局部变量等。
注意事项:不要返回局部变量的地址,因为局部变量的生命周期仅限于所在函数的执行期间。当函数返回时,局部变量所在的栈帧就会被销毁,栈区中的内存空间也会被释放。
//栈区数据注意事项:不要返回局部变量的地址
//栈区的数据由编译器管理开辟和释放
int* func(int b)//形参b 也放在栈区
{
b = 100;
int a = 10;//局部变量存放在栈区,栈区的数据在函数执行完后自动释放
return &a;//返回局部变量的地址
}
void main()
{
int* p = func(10);
cout << *p << endl;//第一次可以打印正确的数字,是因为编译器做了保留
cout << *p << endl;//第二次这个数据就不再保留了
cout << endl << endl << endl;
system("pause");
}
堆区
由程序员分配释放若程序员不释放,程序结束时由操作系统回收在C++中主要利用new在堆区开辟内存(在C语言程序中用malloc函数动态分配内存)。
不同存储类型的分配(auto,register,static,extern)
auto存储类型:auto只能用来标识局部变量的存储类型,对于局部变量,auto是默认的存储类型,不需要显示的指定。因此,auto标识的变量存储在栈区中。
register存储类型:声明为register的变量在由内存调入到CPU寄存器后,则常驻在CPU的寄存器中,因此访问register变量将在很大程度上提高效率,因为省去了变量由内存调入到寄存器过程中的好几个指令周期。在C++中,例如 while(i–){}; 对变量 i 有频繁的操作,编译器会将其存储在寄存器中。
static存储类型:被声明为静态类型的变量,无论是全局的还是局部的,都存储在数据区中,其生命周期为整个程序,如果是静态局部变量,其作用域为一对{}内,如果是静态全局变量,其作用域为当前文件。静态变量如果没有被初始化,则自动初始化为0。静态变量只能够初始化一次。
extern存储类型:extern用来声明在当前文件中引用在当前项目中的其它文件中定义的全局变量。如果全局变量未被初始化,那么将被存在BBS区中,且在编译时,自动将其值赋值为0,如果已经被初始化,那么就被存在数据区中。
存储类型 | 作用域 | 生命周期 | 存储位置 | 声明方式 |
---|---|---|---|---|
auto变量 | 一堆{}内 | 当前函数 | 变量默认存储类型,存储在栈区 | 定义的函数内 |
register | 一堆{}内 | 当前函数 | 运行时存储在CPU寄存器中 | 定义的函数内,使用关键字register |
static全局变量 | 当前文件 | 整个程序运行期间 | 初始化再data段,未初始化在bss段 | 所有函数外,使用关键字static |
static局部变量 | 一对{内} | 整个程序运行期间 | 初始化再data段,未初始化在bss段 | 定义的函数内,使用关键字static |
static函数 | 当前文件 | 整个程序运行期间 | 代码区 | |
extern变量 | 整个程序 | 整个程序运行期间 | 初始化再data段,未初始化在bss段 | 所有函数外 |
extern函数 | 整个程序 | 整个程序运行期间 | 函数默认存储类型,代码段 | 所有函数外 |
字符串常量 | 当前文件 | 整个程序运行期间 | 数据段 |
示例代码
#include <iostream>
using namespace std;
int a = 0;//初始化的全局变量:保存在数据段
char *p1;//未初始化的全局变量:保存在BSS段
int main()
{
int b;//未初始化的局部变量:保存在栈上
char s[] = "abc";//"abc"为字符串常量保存在常量区;数组保存在栈上,并将常量区的"abc\0"复制到该数组中。这个数组可以随意修改而不会有任何隐患,而"123"这个字符串依然会保留在静态区中。
char * p = "hello";//其中 p 存储在栈区,内容hello 存储在 data区。
char *p2;//p2保存在栈上
char *p3 = "123456";//p3保存在栈上,"123456\0"保存在数据区的只读部分data区
//注意:如果令p3[1] = 9; 则程序崩溃,指针可以访问但不允许改变常量区的内容
//声明了一个指针p3并指向"123456\0"在静态区中的地址,事实上,p3应该声明为char const *,以免可以通过p3[i]='\n'这一类的语法去修改这个字符串的内容。如果这样做了,在支持“常量区”的系统中可能会导致异常,在“合并相同字符串”的编译方法下会导致其它地方的字符串常量发生变化。
static int c = 0;//初始化的静态局部变量:保存在数据区(数据段)
p1 = (char *)malloc(sizeof(char) * 10);//分配的10字节区域保存在堆上
p2 = (char *)malloc(sizeof(char) * 20);//分配的20字节区域保存在堆上
strcpy(p1, "123456");
//"123456\0"放在常量区,编译器可能会将它与p3所指向"123456"优化成一个地方
return 0;
}