内存布局
C++的内存分为5大区,按照地址从高位到低位的顺序,分别为栈区,堆区,BBS区,数据区,代码区。
栈区
编译器自动管理分配,存放程序中的局部变量,函数参数值,返回变量。内存分配从高地址向低地址增长。
堆区
程序中用户自定义动态分配的内存区域,内存分配从低地址向高地址增长。
BBS区
存放程序中被显示初始化为0的全局变量和静态变量;以及未被初始化的全局变量和静态变量。
数据区
存放已初始化的全局变量,静态变量,常量数据(如字符串常量)。
代码区
存放可执行程序的机器码。
下图是内存布局的基本结构

函数栈
如上图所示,可执行程序文件包含BBS,数据区和代码区,可执行程序载入内存后系统会保留一些空间,堆区和栈区。堆区主要是动态分配的内存(默认情况下),而栈区主要是函数以及局部变量等。
当调用函数时,一块连续内存压入栈;函数返回时,堆栈帧弹出。
堆栈帧包含如下数据:
1).函数返回地址
2).局部变量/CPU寄存器数据备份
以main调用fa(),fa调用fb(),来举例说明函数调用压栈过程,如下图所示:

函数调用结束后会依次出栈,如下图所示:

全局变量
当全局变量,静态变量未被初始化的时候,他们记录在BBS段。

处于BBS段的变量值默认为0,考虑到这一点,BBS段内部无需存储大量的零值,只需记录字节个数即可。BBS段主要是为了节省可执行文件在磁盘上所占空间,它只记录变量所需的大小。对未初始化的大型数组的节省效率比较明显,举例如下:

在上述程序中,若不存在BBS段,可执行文件将开辟10000*sizeof(int)大小的空间,并全部存储为0,该变量将在磁盘上占用39KB的空间,但是若存在BBS段,则在可执行文件中,将只是记录现在BBS段总大小40000即可,无需占据39KB的空间。
系统载入可执行程序后,将BBS段的数据载入数据段,并将内存初始化为0,再调用程序入口main函数。
内存对齐
对于基本类型,如float,double,int,char等,他们的大小和内存占用是一致的。但是对于结构体而言,我们取得其sizeof的结果,会发现这个值可能会大于结构体成员大小的总和,这是由于结构体内部成员进行了内存对齐。
进行内存对齐有2个好处:
1).内存对齐使数据读取更高效
在硬件设计中,数据读取的处理器只能从地址为k的倍数内存处开始读取数据,这种读取方式相当于将内存分为多个块,如果内存可以从任意位置开始存放的话,数据很可能会被分散到多个块中,处理分散在多个块中的数据需要移除首位不需要的字节,然后进行合并,非常耗时。为了提高数据的读取效率,程序分配的内存不是连续存储的,而是按首地址为k的倍数方式存储,这样就可以一次性读取数据,而不需要额外的操作。
2).在某些平台上,不进行内存对齐会崩溃
不是所有的硬件平台CPU都能访问任意地址上的数据,某些硬件平台只能在某些地址处取得特定类型数据,否则会抛出硬件异常。
内存对齐规则
定义有效对齐值为结构体中最宽成员和编译器/用户指定对齐值中较小的那个。
1).结构体起始地址为有效对齐值的整数倍
2).结构体总大小为有效对齐值得整数倍
3).结构体第一个成员偏移值为0,之后成员的偏移值为min(有效对齐值,自身大小)的整数倍。
相当于每个成员要进行对齐,并且每个结构体也要进行对齐。


内存碎片
程序的内存往往不是紧凑连续排布的,而是存在着许多碎片。我们跟据碎片的原因把碎片分为内部碎片和外部碎片两张类型:
1).内部碎片:系统分配的内存大于实际所需的内存(内存对齐机制)
2).外部碎片:不断分配回收不同大小的内存,由于内存分布散乱,较大内存无法分配。

继承类布局
继承
如果一个类继承自另一个类,那么它自身数据位于父类之后。
含虚函数的类
如果当前类包含虚函数,则会在类的最前端占用4个字节,用于存储虚表指针,它指向一个虚函数表, 虚函数表中包含当前类的所有虚函数指针。
虚函数表 下一章详细讲解。