C++基础学习之2 - 内存对象模型

来源:互联网 发布:ubuntu 16.04分区方案 编辑:程序博客网 时间:2024/05/02 22:24

        内存对象模型 是同学们在面试过程中经常会被问到的问题,一堆的 Sizeof 求答案,怎么破?听作者讲完本节,也许你会有个简单的认识,先说对于C++,其内存是如何存放的:

        先说说C++的存储区,有4种类型,堆、栈、全局存储区、常量存储区

堆:  通过new来初始化,delete释放,一般是由程序员来创建和管理;

栈:  栈 是指临时或局部变量的存放位置,一般由操作系统分配和释放,比如我们常说的压栈、出栈;

全局存储区:  全局存储区 是指 程序的 全局变量、静态变量 的存放位置,独立于类对象,程序结束时释放;

常量存储区:  和全局存储区类似,用于常量数据的存放;

class BaseClass; // 前向声明void main(){        int i = 16;  // 栈        char* str = new char[16];  // 堆        BaseClass b;  // 类对象分配到栈}class BaseClass{public:        int ID;        char* m_strName;        Static int m_nCountry;  // 全局存储区public:        void setID(int _id) { ID=_id; }        static void Show() {……}};const int nCountryNum = 224;  // 常量存储区
        从上面代码可以清晰的看到内存的分布情况,这里需要展开的是,当类对象分配到堆或者栈上的时候,其Sizeof的情况。

        那么上面代码中的 sizeof 是多大呢?我们运行的结果是 8,也就是sizeof(int)+sizeof(char*),为什么呢?根据我们前面的描述我们知道,静态变量和函数放在全局区里面,不占用对象存储空间,占用存储空间的只有前两个。

        我们再来看下面几种情况:

class BaseClass1{};cout << sizeof(BaseClass1); // 输出为1 - 空类的sizeof为1class BaseClass2{public:        BaseClass2();        virtual ~BaseClass2(); // 虚函数,访问虚函数表protected:        virtual void init();};cout << sizeof(BaseClass2); // 输出为4 - 增加了虚函数表地址,但与虚函数个数无关class DeriveClass : public BaseClass2{public:        int m_nType;};cout << sizeof(DeriveClass); // 输出为8 - 父子变量相加

        我们来总结一下:

1. 空类的 Sizeof等于1;

2. 带有虚函数的 Sizeof 需要额外计算虚函数表的指针;

3. 继承模式下 父子变量相加,虚函数表也计算在内。

        说到这里,基本已经说清楚了,接下来我们来看两个专题,可重入函数 和 菱形继承问题

** 可重入函数:

        当出现多个任务调用同一个函数的时候,如果能保证 每次调用得到一样的结果,我们认为该函数是可重入的,反之,函数是不可重入的,不可重入函数一般也称为不安全函数。

        那么导致函数不可重入的因素有哪些呢?

1. 函数内部使用了静态或者全局变量,而这些变量的值有可能每次并不一致;

2. 使用堆导致分配位置不同,或者IO带来的不一致输入;

3. 调用了其他不可重入的函数;

        事实上,我们对可重入函数的定位,通常仅仅用于计算,我们给定了input,里面所有用的临时变量都在栈内分配。

        可重入函数 与 线程安全 没有必然的联系,可重入只是针对单线程反复调用来讲,线程安全的保证方式在于加锁,即:

1. 可重入的函数一定是线程安全的;

2. 线程安全的函数也不一定是可重入的; 


** 菱形继承问题:

        菱形继承问题也叫钻石继承问题,描述为 两个类继承自同一父类,同时又有子类同时继承这两个类,图示如下:


        通过一段代码来看:

class Base{        int a;};class Parent1 : public Base{        int b;};class Parent2 : public Base{        int c;};class Derived : public Parent1, public Parent2{        int d;};

        先来看一个Key Problem,编译不过,Base的成员变量在 Derived 里面有几份Copy? 没错,是两份,每个父类保存了一份,这种二义性显然是不允许的。

        解决方案1:尽量避免使用双继承,确实这并不是一个清晰的集成体系;

        解决方案2:感谢C++给出了一个解决方案,那就是 虚继承,专门针对这个问题给出的(姑且算个补丁吧,出来这摊就废了),我们把上面的类改造一下:

class Base{        int a;};class Parent1 : virtual public Base{        int b;};class Parent2 : virtual public Base{        int c;};class Derived : public Parent1, public Parent2{        int d;};

        好简单,就这样,Derived 类就只保留一个 a 对象,解决了二义性。

        最后再来看,虚继承 的sizeof,与虚函数类似,虚继承添加了一个 虚基类表 指针(4字节)

        很明显 Sizeof(Parent1) = Sizeof(a) + Sizeof(b) + 虚基类指针 = 12

                    Sizeof(Derived) = Sizeof(a) + Sizeof(b) + Sizeof(c) + Sizeof(d) + 虚基类指针 * 2 = 24

详细的内容描述也可以参考(这篇作者认为写的还是不错):

http://www.cnblogs.com/QG-whz/p/4909359.html

2 0
原创粉丝点击