zoukankan      html  css  js  c++  java
  • c++ 对象内存分配和虚函数

    1. c++类对象(不含虚函数)在内存中的分布

        c++类中有四种成员:静态数据、非静态数据、静态函数、非静态函数。 
    1. 非静态数据成员放在每个对象内部,作为对象专有的数据成员 
    2. 静态数据成员被抽取出来放在程序的静态数据区内,为该类所有对象共享,只保留一份 
    3. 非静态成员函数和静态成员函数最终都被提取出来放在程序的代码段中并为该类所有对象共享,因此每个成员函数也只能存在一份代码实体

        因此,构成对象的只有数据,任何成员函数都不属于任何一个对象,非静态成员函数和对象的关系就像是绑定,绑定的中介就是this指针 
    1. 不含有虚函数的对象在内存中分配 
        如下代码示例:

    class Test3{
    public:
    	void f(){
    		cout << "f in Test3" << endl;
    	}
    	static int a;
    };
    class Test4{
    public:
    	void f(){
    		cout << "f in Test4" << endl;
    	}
    	int a;
    };
    
    class Test5{
    public:
    	void f(){
    		cout << "f in Test5" << endl;
    	}
    	int a;
    	char c;
    };
    struct Test6{
    	char c[];
    };
    int main(){
    	Test3 test3;
    	cout << "size of Test3 = " << sizeof(Test3) << endl;    //1, 不含有非静态数据
    	cout << "size of test3 = " << sizeof(test3) << endl;    //1
    
    	Test4 test4;
    	cout << "size  of Test4 = " << sizeof(Test4) << endl;   //4 含有非静态数据int
    	cout << "size  of test4 = " << sizeof(test4) << endl;   //4
    
    	cout << "size  of Test5 = " << sizeof(Test5) << endl;   //8,含有非静态数据 int和char
    	//这里为8,显然是int 和 char对齐之后的结果。
    	
    	cout << "size  of Test6 = " << sizeof(Test6) << endl;   //1,结构体或类中,空数组,大小为1
    	return 0;
    }
    

        从以上的代码示例中可以看出,类的对象(不含有虚函数)的size值等于对象中所有非静态成员对齐后的size之和。对齐规则同结构体,详见 结构体对齐. 
        同时也有,若结构体或类中不含有任何数据,sizeof结果为1;若结构体或类中只有空数组,不占用空间,sizeof结果仍为1;若结构体或类含有数据,最后添加了空数组,空数组不占空间,sizeof为去掉空数组之后的对齐后大小.

        那么,如果知道了对象在内存中的地址,就可以得到各个非静态成员的地址,然后可以强行修改,如:

        Test3 test4;
        int* p = (int*)&test4;
        *p = 200; //此时test4对象内部的a值就被修改了
    

    但这种方式很不安全,强烈不推荐!

    2. 虚函数

        c++中通过虚函数来实现运行时多态,主要方法如下:

        编译器对每个包含虚函数的类创建一个表(称为 VTABLE,虚函数表),在VTABLE中,编译器放置特定类的虚函数地址。在每个带有虚函数的类对象中,编译器秘密的内置一个指针,称为 vpointer(VPTR),指向这个对象的VTABLE。通过基类指针做虚函数调用时(也就是做多态调用时),编译器静态的插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,这样就能够调用正确的函数使晚捆绑发生。 
        为每个类设置VTABLE,初始化VPTR,为虚函数插入调用代码,所有这些都是自动发生的。利用虚函数,这个对象的合适的函数就能被调用,哪怕在编译器还不知道这个对象的特定类型的情况下。(《c++编程思想》)

    3. 虚函数表在内存中分布

        对于有虚函数的基类和子类来说,内存中类对象的开始位置就是4字节的VPTR(虚函数表的地址,指向VTABLE)。如果一个子类继承自基类,且没有对虚函数进行重写,子类仍然会自己维护一个虚函数表,和基类的虚函数表地址不同(尽管可能内容相同)。 
        虚函数表中存放该类对应的实际会调用的函数地址(即若子类没有覆盖基类的虚函数,则对应位置存放基类虚函数地址;若子类覆盖了基类的虚函数,则对应位置存放子类的虚函数地址)。虚函数表以NULL结尾。且如果子类中添加了基类中没有的虚函数,则新加的虚函数被放在子类虚函数表最后。 
        虚函数表只存放虚函数的地址,非虚函数由编译器在编译期间静态设定。 
        如果一个子类是多继承,即含有多个基类,则有多个虚函数表,分别对应到不同的继承。

        可以通过对象的地址,得到虚函数表,单独调用类中的函数:

    class A{
    public:
    	A(){
    	}
    	~A(){
    	}
    	virtual void f1(){
    		cout << "f1 in class A" << endl;
    	}
    };
    class B{
    public:
    	virtual void f2(){
    		cout << "f2 in clas B" << endl;
    	}
    };
    class D: public A, public B{
    public:
    	D(){
    	}
    	~D(){
    	}
    	void f1(){
    		cout << "f1 in class D" << endl;
    	}
    	void f2(){
    		cout << "f2 in class D" << endl;
    	}
    };
    typedef void(*Func)();
    int main(){
    	D* d = new D();
        uint32_t* vtable_ptr = (uint32_t*)*(uint32_t*)d;//d为对象地址,*d即可获得VPTR值
    	Func f = (Func)(*vtable_ptr);//*VPTR即为第一个虚函数的地址
    	cout << "func's address is : " << *(uint32_t*)f << endl;
    	f();    //调用,输出 "f1 in class D"
    	return 0;
    }
    

    4. 单继承的对象在内存中的分配 
        单继承的对象,如果含有虚函数,则对象在内存中的布局为:开头为VPTR,然后为该对象的非静态成员变量,且为先基类的变量后子类的变量。且如果父类的非静态成员变量为private,子类中仍然会存在那些private的非静态数据

    class Test4{
    public:
    	virtual void f(){
    		cout << "f in Test4" << endl;
    	}
    	int get_a(){
    		return a;
    	}
    	int a;
    	char c;
    };
    class Test4{
    public:
    	virtual void f4(){
    		cout << "f4 in Test4" << endl;
    	}
    	int get_a(){
    		return a;
    	}
    private:
    	int a;
    	char c;
    };
    int main(){
    	int* pa = (int*)((char*)(&test4) + 4);  //+4,是为了跳过VPTR
    	cout << "first member in test4 = " << *pa << endl;  //输出一个随机数
    	*pa = 200;
    	cout << "first member in test4 = " << test4.get_a() << endl; //输出200
    	
    	Test7 test7;
    	cout << "sizeof test7 = " << sizeof(test7) << endl;
    	//12字节,4字节的VPTR, 4字节的int a, 4字节(对齐后的)char c。
    	pa = (int*)((char*)(&test7) + 4);
    	cout << "first member in test7 = " << *pa << endl;
    	*pa = 1000;
    	cout << "first member in test7 = " << test7.get_a() << endl; //输出1000
    	return 0;
    }
    

    结构图大致如下: 

    5. 多继承的对象在内存中的分配 
        如果一个子类有多个基类,为多继承。其对象在内存中的布局为:按照多个继承,分为多个块;每块的开头为一种继承的虚函数表,接着为该种继承的基类的非静态成员变量(包括public,protected,private);所有块结束之后,为该子类特有的非静态成员变量;如果该子类又新加了虚函数,则虚函数放在第一个虚函数表的最后。

    class A{
    public:
        virtual void f1(){
            cout << "f1 in class A" << endl;
        }
    private:
        int a;
    };
    class B{
    public:
        virtual void f2(){
            cout << "f2 in class B" << endl;
        }
        virtual void f4(){
            cout << "f4 in class B" << endl;
        }
        int b;
    }
    class D:public A, B{
    public:
        void f1(){
            cout << "f1 in class D" << endl;
        }
        void f2(){
            cout << "f2 in class D" << endl;
        }
        virtual void f3(){
            cout << "f3 in class D" << endl;
        }
    private:
        double c;
    }
    

        以上类层次结构所对应的对象在内存中的布局为: 
                

    6. 虚基类和虚继承

        如果有多个类继承自同一个基类,最后又被同一个子类继承这些类(例如 B, C继承自A, D继承自B,C),这种情况下,如果不使用虚继承,若类型A存在非静态成员变量a,则会有D类型的对象保留从B继承来的a和从C继承来的a,出现空间冗余。 
        而若使用虚继承,可以使得D从B继承来的a和从C继承来的a是同一个,节省空间。 
    没有虚继承的代码和内存布局

    class A{
    public:
    	virtual void f1(){
    		cout << "f1 in class A" << endl;
    	}
    	virtual void f2(){
    		cout << "f2 in class A" << endl;
    	}
    private:
    	int a;
    };
    class B:public A{
    public:
    	void f1(){
    		cout << "f1 in class B" << endl;
    	}
    	virtual void f4(){
    	    cout << "f4 in class B" << endl;
    	}
    };
    class C:public A{
    public:
    	void f2(){
    		cout << "f2 i class C" << endl;
    	}
    	virtual void f3(){
    		cout << "f3 in class C" << endl;
    	}
    };
    class D :public B, C{
    public:
    	void f3(){
    		cout << "f3 in class D" << endl;
    	}
    	int d;
    };
    
    typedef void(*Func)();
    int main(){
    	
    
    	A obj_a;
    	cout << "sizeof obj_a = " << sizeof(obj_a) << endl;
    
    	B obj_b;
    	cout << "sizeof obj_b = " << sizeof(obj_b) << endl;
    
    	C obj_c;
    	cout << "sizeof obj_c = " << sizeof(obj_c) << endl;
    
    	D obj_d;
    	cout << "sizeof obj_d = " << sizeof(obj_d) << endl;
    
    	
    	uint32_t* vptr_b = reinterpret_cast<uint32_t*>(*(uint32_t*)(&obj_d)); //指向vftable_b
    
    	Func b_f1 = (Func)(reinterpret_cast<void*>(*vptr_b));
    	b_f1();
    	Func a_f2 = (Func)(reinterpret_cast<void*>(*(vptr_b + 1)));
    	a_f2();
        Func b_f4 = (Func)(reinterpret_cast<void*>(*(vptr_b + 2)));
        b_f4();
        
    	uint32_t* vptr_c = reinterpret_cast<uint32_t*>(*(uint32_t*)((char*)&obj_d + 8));
    	Func a_f1 = (Func)(reinterpret_cast<void*>(*vptr_c));
    	a_f1();
    	Func c_f2 = (Func)(reinterpret_cast<void*>(*(vptr_c + 1)));
    	c_f2();
    	Func d_f3 = (Func)(reinterpret_cast<void*>(*(vptr_c + 2)));
    	d_f3();
    	return 0;
    }
    

    运行结果 
                         

    内存布局 
                   

    使用虚继承的代码和内存布局 
        使用虚继承之后,公共的基类在子类中只有一份,在多重继承的基础上多了vbtable来存储到公共基类的偏移。

    class A{
    public:
    	virtual void f1(){
    		cout << "f1 in class A" << endl;
    	}
    	virtual void f2(){
    		cout << "f2 in class A" << endl;
    	}
    	int get_a(){
    		return a;
    	}
    private:
    	int a;
    };
    class B:virtual public A{
    public:
    	void f1(){
    		cout << "f1 in class B" << endl;
    	}
    	virtual void f4(){
    		cout << "f4 in class B" << endl;
    	}
    };
    class C:virtual public A{
    public:
    	void f2(){
    		cout << "f2 in class C" << endl;
    	}
    	virtual void f3(){
    		cout << "f3 in class C" << endl;
    	}
    };
    class D :public B, public C{
    public:
    	void f3(){
    		cout << "f3 in class D" << endl;
    	}
    	int get_d(){
    		return d;
    	}
    private:
    	int d;
    };
    
    typedef void(*Func)();
    int main(){
    	
    
    	A obj_a;
    	cout << "sizeof obj_a = " << sizeof(obj_a) << endl;
    
    	B obj_b;
    	cout << "sizeof obj_b = " << sizeof(obj_b) << endl;
    
    	C obj_c;
    	cout << "sizeof obj_c = " << sizeof(obj_c) << endl;
    
    	D obj_d;
    	cout << "sizeof obj_d = " << sizeof(obj_d) << endl;
    
    	cout << "d in obj_d = " << obj_d.get_d() << endl;
    	int* d_ptr = (int*)((char*)&obj_d + 16);
    	*d_ptr = 200;
    	cout << "d in obj_d = " << obj_d.get_d() << endl;
    
    	cout << "a in obj_d = " << obj_d.get_a() << endl;
    	int* a_ptr = (int*)((char*)&obj_d + 24);
    	*a_ptr = 1000;
    	cout << "a in obj_d = " << obj_d.get_a() << endl;
    
    
    	uint32_t* vptr_b = reinterpret_cast<uint32_t*>(*(uint32_t*)(&obj_d)); //指向vftable_b
    
    	Func b_f4 = (Func)(reinterpret_cast<void*>(*vptr_b));
    	b_f4();
    
    	uint32_t* vptr_c = reinterpret_cast<uint32_t*>(*(uint32_t*)((char*)&obj_d + 8));
    	Func c_f3 = (Func)(reinterpret_cast<void*>(*(vptr_c)));
    	c_f3();
    
    	uint32_t* vptr_a = reinterpret_cast<uint32_t*>(*(uint32_t*)((char*)&obj_d + 20));
    	Func a_f1 = (Func)(reinterpret_cast<void*>(*(vptr_a)));
    	a_f1();
    
    	Func a_f2 = (Func)(reinterpret_cast<void*>(*(vptr_a + 1)));
    	a_f2();
    
    	uint32_t* ptr = (uint32_t*)((char*)&obj_d + 4);
    	cout << "ptr = " << *ptr << endl;
    
    	ptr = (uint32_t*)((char*)&obj_d + 12);
    	cout << "ptr = " << *ptr << endl;
    
    	cout << "vptr.a = " << reinterpret_cast<uint32_t>(vptr_a) << endl;
    
    	return 0;
    	
    }
    

    运行结果 
                        

    内存布局 
    不同编译器可能不同,在vs2013上测试 
                        

        其中,继承的公共基类A只保留一份数据,且D中对应到基类B和C的虚函数表(vftable@B和vftable@C)中只保留在B或C中新引入的虚函数,而B和C从A中继承的公共虚函数放在vftable@A中。 
        vftable@B之后为vbtable@B,指向vftable@A,vftable@C之后为vbtable@C,指向vftable@A.

    参考

    http://blog.csdn.net/starryheavens/article/details/4549616
    http://blog.sina.com.cn/s/blog_60e96a410100lirk.html

  • 相关阅读:
    VXDIAG VX Manager V1.8.0.0814更新指南
    Java Stream 源码分析
    RESTful接口实战
    Java面经
    开发中遇到的问题:push to origin/master was rejected错误解决方案
    开发遇到的问题:springboot问题:Unable to find main class
    java封神之路-stream流-进阶
    Java封神之路-stream流-入门
    Java封神之路-设计模式-工厂模式
    JAVA封神之路-设计模式-单例模式
  • 原文地址:https://www.cnblogs.com/gtarcoder/p/4929927.html
Copyright © 2011-2022 走看看