C++学习总结2——C++内存模型

为了更直观的理解这部分内容,使用如下的程序实例进行说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#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

rodataread only data的缩写,只读区域,像上面程序中的pi和常量字符串”count % 10=”和”i=”都保存在该区域。

text

text区保存程序编译链接后生成的机器代码。当调用函数时,会将该区域的机器代码加载到栈中执行。

因为rodata区和text区在程序运行过程中都是不能修改的,所以在程序启动时,这两个区域又被放到一个叫做常量区的箱子中,并且在箱子外面贴上”不许修改”的标签,以防该区域的内容被修改。

data

data中存放已经初始化的 全局变量和被声明为static的局部变量。像上面程序中的全局语句“static int out=0;”,“int i=1;”以及main函数中的“static int out=2;”,这些语句定义的变量都已经被初始化,所以存放在data区。注意我这里给全局静态变量和局部静态变量起了相同的名字,都叫out,但在main函数里面输出的out=2,说明虽然都是在data区,但编译和链接过程中全局变量和局部变量的标识还是不同的,编译器不会因为名字相同而混淆两者。

bss

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区中保存的都是全局变量和静态局部变量(跟全局变量性质一致),所以在程序启动时,这两个区域又被放到一个叫做全局区的箱子中,这个箱子中的内容是可读可写的。

heap

堆区用来存放程序运行过程中动态分配的内存。像new和malloc就在该区域上申请内存空间。该区域内存的管理必须由程序写作者来负责,也就是如果通过new或malloc申请了一块内存,在程序结束时必须通过delete或free来释放相应的内存。new和delete的内容我后面会仔细说明。

因为该区域可以由用户来申请,申请大小视情况而定,通常很不一致,所以很容易造成该区域内存的碎片化。

堆内存的大小很大,一般来讲,在32位系统下,可以达到4G,所以通常不会溢出。

stack

栈区保存函数的参数和函数内声明的变量,但声明为static的局部变量除外。栈具有后进先出的特点,很适合函数的一层层调用,所以函数调用时的变量都保存到该区中。上面程序中的main函数和func1,func2中的非static类型的变量在调用时都会加载到该区域。

栈的大小是很有限的,在Visual Studio中,默认的栈大小是1M,超过1M就会出现“stack overflow”的错误,可以通过修改默认设置来提高栈大小。