C++内存模型 - MrYun - 博客园 (cnblogs.com)
内存区域
C++内存分为5个区域:
堆 heap :
由new分配的内存块,其释放编译器不去管,由我们程序自己控制(一个new对应一个delete)。如果程序员没有释放掉,在程序结束时OS会自动回收。涉及的问题:“缓冲区溢出”、“内存泄露”
栈 stack :
是那些编译器在需要时分配,在不需要时自动清除的存储区。存放局部变量、函数参数。
存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也就自动释放了。
全局/静态存储区 (.bss段和.data段) :
全局和静态变量被分配到同一块内存中。在C语言中,未初始化的放在.bss段中,初始化的放在.data段中;在C++里则不区分了。
常量存储区 (.rodata段) :
存放常量,不允许修改(通过非正当手段也可以修改)
代码区 (.text段) :
存放代码(如函数),不允许修改(类似常量存储区),但可以执行(不同于常量存储区)
根据C++对象生命周期不同,C++的内存模型有三种不同的内存区域:
1.自由存储区,动态区、静态区局部非静态变量的存储区域(栈)
2.动态区:用operator new,malloc分配的内存(堆)
3.静态区:全局变量、静态变量、字符串常量存在位置
c++中类对象的内存模型(布局)是怎么样的?
答:一般遵循以下几点原则:
(1)如果是有虚函数的话,虚函数表的指针始终存放在内存空间的头部;
(2)除了虚函数之外,内存空间会按照类的继承顺序(父类到子类)和字段的声明顺序布局;
(3)如果有多继承,每个包含虚函数的父类都会有自己的虚函数表,并且按照继承顺序布局(虚表指针+字段);如果子类重写父类虚函数,都会在每一个相应的虚函数表中更新相应地址;如果子类有自己的新定义的虚函数或者非虚成员函数,也会加到第一个虚函数表的后面;
(4)如果有钻石继承,并采用了虚继承,则内存空间排列顺序为:各个父类(包含虚表)、子类、公共基类(最上方的父类,包含虚表),并且各个父类不再拷贝公共基类中的数据成员。
【游戏开发面经汇总】- 计算机基础篇 - 知乎 (zhihu.com)
C++11中的内存模型 - 简书 (jianshu.com)
C++内存布局(上)_c++内存布局(上)-CSDN博客
单一虚继承(含成员变量+虚函数+虚函数覆盖)
继承关系如下:
所谓的虚继承就是把继承语法前加上virtual
关键字,例如class B:virtual public A{..};
虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的 。内存分布是这样的:
这里需要解释下,因为出现了vfptr
与vbptr
,前面的我们已经经常看到了,但是vbptr
却是第一次见,它是CChildren
对应的虚表指针,它指向CChildren
的虚表vtable,另一个vfptr
位于0地址偏移处,它指向vftable。从截图中也可以看出有两个表vftable
与vbtable
。第二张vbtable
中的8表示vbptr
与基类的vfptr
之间的偏移。
内存布局为:
另外提及一下,如果CChildren
里全部是重载基类中的虚函数的话,或者说没有新的虚函数的话,vftptr
指向的虚函数表就是空的,所以计算大小的时候可以不用算进去,因为实际上并没有创建相应的表格:
- 钻石(菱形)继承存在什么问题,如何解决?
【参考资料】:C++之钻石问题和解决方案(菱形继承问题)_Benson的专栏-CSDN博客、C++:钻石继承与虚继承 - Tom文星 - 博客园 (cnblogs.com)
答:会存在二义性的问题,因为两个父类会对公共基类的数据和方法产生一份拷贝,因此对于子类来说读写一个公共基类的数据或调用一个方法时,不知道是哪一个父类的数据和方法,也会导致编译错误。可以采用虚继承的方法解决这个问题(父类继承公共基类时用virtual修饰),这样就只会创造一份公共基类的实例,不会造成二义性。
- 堆是C语言和操作系统的术语、是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。
- new所申请的内存区域在C++中称为自由存储区。藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。
堆和栈的区别
- 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
- 空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,默认的栈空间大小是1M(VS2017 项目-属性-链接器-系统可以修改)。
- 碎片问题:对于堆来讲,频繁的malloc/free势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。栈是先进后出的队列,以至于永远都不可能有一个内存块从栈中间弹出。
- 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
- 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
C++11中的内存模型 - 简书 (jianshu.com)