为了更直观的理解这部分内容,使用如下的程序实例进行说明:
#include<iostream>
using namespace std;
const double pi=3.1415926; //常量
static int out=0; //静态全局变量
int i=1; //初始化了的全局变量
int j; //未初始化的全局变量
void func1()
{
static int count; //静态局部变量
count++;
int i=count % 10; //局部变量
cout<<"count % 10="<<i<<endl; //"count % 10="为字符串常量
}
void func2()
{
int i=0; //局部变量
int *pi=&i; //局部变量
*pi=*pi+1;
cout<<"i="<<i<<endl; //"i="为字符串常量
}
int main()
{
static int out=2; //静态局部变量
cout<<"out="<out<<endl;
func1();
func2();
return 0;
}
在这个例子中我尽量表现了各种情况,虽然写得很不合理…
先给出C++内存的一个模型图:
对于一个C++程序,内存区域分六个部分:依次是rodata
区,text
区,data
区,bss
区,heap
区和stack
区。
其中rodata
区和text
区在加载时会合并到一个段中,该段称为常量区,该区域的内容只允许读,不允许修改;
data
区和bss
区在加载时合并到一个段中,该段被称为全局区,其中的内容,对程序来说,是可读可写的。
每个区的详细说明如下。
rodata
是read only data
的缩写,只读区域,像上面程序中的pi和常量字符串”count % 10=”和”i=”都保存在该区域。
text
区保存程序编译链接后生成的机器代码。当调用函数时,会将该区域的机器代码加载到栈中执行。
因为rodata
区和text
区在程序运行过程中都是不能修改的,所以在程序启动时,这两个区域又被放到一个叫做常量区的箱子中,并且在箱子外面贴上”不许修改”的标签,以防该区域的内容被修改。
data
中存放已经初始化的 全局变量和被声明为static的局部变量。像上面程序中的全局语句“static int out=0;”,“int i=1;”以及main函数中的“static int out=2;”,这些语句定义的变量都已经被初始化,所以存放在data区。注意我这里给全局静态变量和局部静态变量起了相同的名字,都叫out,但在main函数里面输出的out=2,说明虽然都是在data区,但编译和链接过程中全局变量和局部变量的标识还是不同的,编译器不会因为名字相同而混淆两者。
bss
是block started by symbol的缩写,该区域存放未初始化的 全局变量和被声明为static的局部变量。在加载时该区域的值会被全部设置为0(对算术类型)或NULL(对指针类型)。上面程序中的全局语句“int j;”和func1中的语句“static int count;”中定义的j和count都在bss
区。
为什么要区分初始化和未初始呢?是为了节省空间。实际上,在目标文件中,未初始化的全局变量和声明为static的局部变量不占有任何空间,只是保存了在运行时它们要占的空间的大小。在运行时开辟同样大小的空间,然后将其全部置为0。所以bss
区也被戏称为“Better Save Space”。
因为data
区和bss
区中保存的都是全局变量和静态局部变量(跟全局变量性质一致),所以在程序启动时,这两个区域又被放到一个叫做全局区的箱子中,这个箱子中的内容是可读可写的。
堆区用来存放程序运行过程中动态分配的内存。像new和malloc就在该区域上申请内存空间。该区域内存的管理必须由程序写作者来负责,也就是如果通过new或malloc申请了一块内存,在程序结束时必须通过delete或free来释放相应的内存。new和delete的内容我后面会仔细说明。
因为该区域可以由用户来申请,申请大小视情况而定,通常很不一致,所以很容易造成该区域内存的碎片化。
堆内存的大小很大,一般来讲,在32位系统下,可以达到4G,所以通常不会溢出。
栈区保存函数的参数和函数内声明的变量,但声明为static的局部变量除外。栈具有后进先出的特点,很适合函数的一层层调用,所以函数调用时的变量都保存到该区中。上面程序中的main函数和func1,func2中的非static类型的变量在调用时都会加载到该区域。
栈的大小是很有限的,在Visual Studio中,默认的栈大小是1M,超过1M就会出现“stack overflow”的错误,可以通过修改默认设置来提高栈大小。