zoukankan      html  css  js  c++  java
  • C++对象布局

    《C++应用程序性能优化》《深度探索C++对象模型》笔记

    #include<iostream>
    using namespace std;
    class student
    {
    public:
    	static int count;
    	int value;
    	char c;
    	student();
    	virtual ~student();
    	int getvalue();
    	virtual void foo(void);
    	static void addcount();
    };
    int main()
    {
    	cout << sizeof(student) << endl;
    	return 0;
    }
    

      运行结果:

    解析:静态数据成员static int count 存储在全局/静态存储区中,并不作为对象占据的内存的一部分,sizeof返回的大小不包括count所占据的内存的大小,而非静态数据成员int value和char c存储在对象占据的的内存中,不论是在全局/静态存储区,还是在堆上或栈上。value内存大小是4个字节,c是1一个字节,但是32位计算机为了提高效率按4个字节对齐,因此也占据了4个字节。此外该类还有两个成员函数(其中一个构造函数)和两个虚函数(一个虚析构函数)和一个静态成员函数,在这些函数中,只有虚函数会分配一个虚函数指针,指向虚函数地址表,叫做“虚函数表”,这个虚函数指针会占据4个字节。

    那么虚函数指针存放在哪里呢?现在写一个程序验证一下虚函数指针存放的位置:

    #include<iostream>
    using namespace std;
    class student
    {
    public:
    	static int count;
    	int value;
    	char c;
    	student() {};
    	virtual ~student() {};
    	int getvalue();
    	virtual void foo() {};
    	static void addcount();
    };
    int main()
    {
    	student stu;
    	cout << &stu << endl;
    	cout << &stu.value << endl;
    	cout << &stu.c << endl;
    	return 0;
    }
    

      运行结果如下:

    从上图可知,对象的起始地址是0x003afd10,接着是value的地址0x003afd14两个地址之间相差4个字节,从这里我们也可以知道,虚函数指针存放在对象开始的4个字节。

    (1)非静态数据成员是影响占据内存大小的主要因素,随着对象数目的增加,非静态数据成员占据的内存会相应的增加。

    (2)所有的对象共享一份静态数据成员,所以静态数据成员占据的内存数量不会随着对象数目的增加而增加。

    (3)静态数据成员函数和非静态数据成员函数不会影响对象内存的大小,虽然其实现会占据相应的内存空间,同样也不会随着数目的增加而增加。

    (4)如果对象中有虚函数会增加一个虚函数指针,不管有多少个虚函数都只有一个虚函数指针。

    单继承

    #include<iostream>
    using namespace std;
    class student
    {
    public:
    	static int count;
    	int value;
    	char c;
    	student() {};
    	virtual ~student() {};
    	int getvalue();
    	virtual void foo() {};
    	static void addcount();
    };
    class student1 :public student
    {
    	int name;
    public:
    	virtual void func() {};
    };
    int main()
    {
    	cout << sizeof(student) << endl;
    	cout << sizeof(student1) << endl;
    	return 0;
    }

      运行结果为:

    解析:我们可以看到派生类的内存大小是16个字节,基类的内存大小是12个字节,相差4个字节,派生类中增加了一个一个int类型的成员变量4个字节,同时也增加了一个虚函数,此时派生类也需要虚函数表,因此我们可知道派生类和基类使用的是同一个虚函数表。或者说,在派生类构造时不再需要创建一张新的虚函数表,而应该在基类的虚函数表中增加或者修改。

    此程序的内存布局图如下:

    问:派生类和基类公用一张虚函数表,且派生类会对虚函数表进行增加和修改,那么如果基类有多个派生类,且多个派生类中都对基类的虚函数进行了重载,那么是如何实现的,会不会有冲突?

    答:这里首先要理解多态的含义,多态是发生在运行时的,只有当一个基类对象指针/引用指向一个派生类对象时才会发生,此时派生类才会对虚函数表进行修改,而不是在编译时就修改的。

    多继承

    #include<iostream>
    using namespace std;
    class student
    {
    public:
    	static int count;
    	int value;
    	char c;
    	student() {};
    	virtual ~student() {};
    	int getvalue();
    	virtual void foo() {};
    	static void addcount();
    };
    class people
    {
    public:
    	virtual void eat() {}
    	int age;
    };
    class student1 :public student,public people
    {
    	int name;
    public:
    	virtual void func() {};
    };
    int main()
    {
    	cout << sizeof(student) << endl;
    	cout << sizeof(people) << endl;
    	cout << sizeof(student1) << endl;
    
    	return 0;
    }
    

      运行结果:

    此程序的内存布局如下:

     虚继承:为了支持虚继承不同 编译器的做法会有所不同,在vs中通过添加一个虚基类表来实现

    #include<iostream>
    using namespace std;
    class student
    {
    public:
    	static int count;
    	int value;
    	char c;
    	student() {};
    	virtual ~student() {};
    	int getvalue();
    	virtual void foo() {};
    	static void addcount();
    };
    class people
    {
    public:
    	virtual void eat() {}
    	int age;
    };
    class student1 :virtual public student, public people
    {
    	int name;
    public:
    	virtual void func() {};
    };
    int main()
    {
    	cout << sizeof(student) << endl;
    	cout << sizeof(people) << endl;
    	cout << sizeof(student1) << endl;
    
    	return 0;
    }
    

      运行结果:

    #include<iostream>
    using namespace std;
    class student
    {
    public:
    	static int count;
    	int value;
    	char c;
    	student() {};
    	virtual ~student() {};
    	int getvalue();
    	virtual void foo() {};
    	static void addcount();
    };
    class people
    {
    public:
    	virtual void eat() {}
    	int age;
    };
    class student1 :virtual public student,virtual public people
    {
    	int name;
    public:
    	virtual void func() {};
    };
    int main()
    {
    	cout << sizeof(student) << endl;
    	cout << sizeof(people) << endl;
    	cout << sizeof(student1) << endl;
    
    	return 0;
    }
    

      运行结果:

    #include<iostream>
    using namespace std;
    class student
    {
    public:
    	static int count;
    	int value;
    	char c;
    	student() {};
    	virtual ~student() {};
    	int getvalue();
    	virtual void foo() {};
    	static void addcount();
    };
    class people
    {
    public:
    	virtual void eat() {}
    	int age;
    };
    class student1 :virtual public student,virtual public people
    {
    	int name;
    public:
    	//virtual void func() {};
    };
    int main()
    {
    	cout << sizeof(student) << endl;
    	cout << sizeof(people) << endl;
    	cout << sizeof(student1) << endl;
    
    	return 0;
    }
    

      运行结果:

    从上面我们可以知道,在虚继承中,每一个基类都会生成一个虚基类表,因此每多一个虚继承内存会增加4,同时如果派生增加一个基类中没有的虚函数也会增加一个指针,内存加4

    问题:类中成员变量的初始化顺序?

    答:因为我们知道初始化的顺序与析构的顺序相反,也就是说,如果类中有两个成员变量A和B,初始化顺序为A->B,那么在这个类被析构时应该先析构B再析构A,而对于一个类来说构造函数可能有多个,初始化列表也有多个,这个在析构时就没法统一,所以就按照成员变量定义时的顺序来。

  • 相关阅读:
    sqlserver中函数和存储过程的区别
    sql 经典面试题
    sqlserver 时间处理函数
    在GROUP BY中"做文章"(五种中简答方法!)
    SQL 非等价连接
    GROUP BY 两个字段(或者多个字段的时候)
    WCF-错误集合001
    DOM
    DOM的排他功能及显示隐藏功能
    预解析
  • 原文地址:https://www.cnblogs.com/wuyepeng/p/9863296.html
Copyright © 2011-2022 走看看