基类和派生类内存详解

     昨天室友问了我一个c++基类和派生类的内存方面的问题,由于当时不能给出确切的答案,所以今天在VS上面一个个测试了一遍,今记录下来。

 问题归结为如下代码:

class A
{
public:
	A():a(0)
	{
		cout<<"this is A's contructor!"<<endl;
	}
	~A()
	{
		cout<<"this is A's destory"<<endl;
	}
	void fun()
	{
		cout<<"this is A::fun()"<<endl;
	}
	virtual void vfun()
	{
		cout<<"this is A::vfun()"<<endl;
	}
public:
	int a;
};

class B:public A
{
public:
	B():a(5)
	{
		cout<<"this is B's contructor!"<<endl;
	}
	~B()
	{
		cout<<"this is B's destory"<<endl;
	}
	virtual void vfun()
	{
		cout<<"this is B::vfun()"<<endl;
	}
        void fun()
        {
                cout<<"this is B::fun()"<<endl;
        }
       int a;
};

<div>int main()
{
	B b;   
	A *p=&b;
	p->fun();
	p->vfun();	
	p->a=2;   //通过p改变A中a的值
	cout<<b.a<<endl;   //b.a=5
	return 0;
}
</div> 
    问题:通过上面的p指针对成员变量a(类A、B中都有a)进行赋值会不会改变B中的a的值?
    解答:因为B继承自A,所以当实例化一个B对象b时,也会构造基类a的部分(我们可以通过成员对象访问符明确指定要访问的对象),因为我们用基类的指针去访问指向派生类的对象,所以会出现对象切割(编译器只会识别到基类的成员对象),所以p->a=2修改的知识p->A::a的值(基类的成员变量a),而b对象的a值依然没有变(B::a==5,A::a=2);

对象b在内存中的内存分布为:


如上图所示,指针p只会取出B中被方框框起来的部分,所以,指针p访问的a并“不是”B的a。(上图中Vt为虚函数表)

为了弄清楚类的内存的详细分配方式,我试图查出虚函数表中的具体内容和类中的成员函数和成员变量的存放位置。

   首先说说我对内存的分配的理解:成员函数时存放在代码区中,一般不会改变,并且该类的多个实例对象共用该代码区,只是每个函数另外会维护一个我们常见的this指针(this指针主要为了标识特定对象的成员变量,防止多个对象之间的成员函数之间串扰,这就解释为什么多个对象共用一个代码区而不会出现问题了,因为每个对象的this指针不一样啊!),然后成员变量主要和对象保存在堆或栈中,这些都是每个对象维护自己一份;

    成员变量和对象在一个连续的空间中,编译器在调用的时候就只要通过简单的内存偏移就可以得到该变量了,那么,对象如何获取该对象所对应的类的成员函数呢?这也很简单,编译器主要通过对象找到其所属的类(编译器一般会维护这样的关系???),然后找到该类的代码区,然后就可以找到相应的成员函数了(虚函数类似,只是虚函数相对于普通函数的静态编译改为动态编译)。

  这只是我个人的理解,如有错误,还请指正!,编写如下测试代码,两个类还是上面两个:

int main()
{
	A a;  //调用默认的构造函数
	A c;

	B b;   
	A *p=&b;
	p->fun();
	p->vfun();

	//cout<<b.a<<endl;  //b.a=5
	cout<<"******A类对象a的内存分配*******"<<endl;
	cout<<"a对象的地址:"<<(int *)&a<<endl;
	cout<<"虚函数A::vfun的地址:"<<&A::vfun<<endl;
	cout<<"虚函数表的第一个函数地址="<<*(int *)*(int *)(&a)<<endl;
	void (*fun)()=(void (*)())(*(int *)*(int *)(&a));
	fun();
	printf("printf虚函数a.vfun的地址=%d\n",&A::vfun);
	cout<<"A中变量a的地址:"<<&a.a<<" "<<&A::a<<endl;
	cout<<"非虚函数fun的地址为:"<<&A::fun<<endl;
	printf("printf非虚函数fun的地址=%X\n",&A::fun);
	cout<<"*******************************"<<endl;

	cout<<"******A类对象c的内存分配*******"<<endl;
	cout<<"c对象的地址:"<<(int *)&c<<endl;
	cout<<"虚函数A::vfun的地址:"<<&A::vfun<<endl;
	cout<<"虚函数表的第一个函数地址="<<*(int *)*(int *)(&c)<<endl;
	fun=(void (*)())(*(int *)*(int *)(&c));
	fun();
	printf("printf虚函数c.vfun的地址=%d\n",&A::vfun);
	cout<<"A中变量c的地址:"<<&c.a<<" "<<&A::a<<endl;
	cout<<"非虚函数fun的地址为:"<<&A::fun<<endl;
	printf("printf非虚函数fun的地址=%X\n",&A::fun);
	cout<<"*******************************"<<endl;

	cout<<"******B类对象b的内存分配******"<<endl;
	cout<<"b的地址&b为:"<<&b<<endl;
	cout<<"b的地址&b为:"<<(int *)&b<<endl;
	cout<<"虚函数b.vfun的地址:"<<&B::vfun<<endl;
	cout<<"虚函数表的第一个函数地址="<<*(int *)*(int *)(&b)<<endl;
	cout<<"虚函数表的第二个函数地址="<<*((int *)*(int *)(&b)+1)<<endl;
	printf("printf虚函数b.vfun的地址=%d\n",&B::vfun);
	fun=(void (*)())(*(int *)*(int *)(&b));
	fun();
	fun=(void (*)())*((int *)*(int *)(&b)+1);
	fun();
	cout<<"B中A的a地址&(b.A::a)为:"<<&(b.A::a)<<endl;
	cout<<"B中非虚函数fun的地址为:"<<&(B::fun)<<endl;
	cout<<"B中a的地址&(b.a)为:"<<&(b.a)<<endl;
	cout<<"*******************************"<<endl;
	
	return 0;
}
 
首先看看A类的两个对象a,c,我想看看其虚函数表的内容,以及成员变量的地址:


怎么????A类的虚函数的地址&(A::vfun)竟然等于1????因为类的成员函数(不管是虚函数还是普通的非静态函数的函数指针),都没有重载输出运算符,编译器默认输出就是1(注意!!!如果是取的静态函数的地址就能得到正确的结果),当我用c风格的printf()函数的时候就能得到正确的结果了:3675337(我输出的时候转换成了十进制了),其他看起来比较正常;

  然后按我之前的理解,虚函数表中保存的虚函数的地址,应该和成员函数指针的到的值应该是一样的,因为都是共用一套函数代码嘛,所以肯定是一样的(这是错误的,下面会分析),另外,如果一个类有多个对象,那么这些对象的虚函数表是怎么分配的呢?是所有的对象共用一个虚函数表,还是每个对象维护一个虚函数表呢?其实上面的输出结果已经很明显了;

     首先,我们通过printf("printf虚函数a.vfun的地址=%d\n",&A::vfun);得到地址3675337;而通过*(int *)*(int *)(&a)得到第一个虚函数表的地址(是A的虚函数地址)为3674817,这里又有问题了,为什么两者的地址有不同呢???难道有多个代码区,而使虚函数的入口函数不一样?通过void (*fun)()=(void (*)())(*(int *)*(int *)(&a));把虚函数表的第一个数据转换为相应的函数指针,然后调用该函数指针所指的函数,得到的输出结果就是对应的虚函数,所以代码肯定是一样的;

  后来查找资料的到如下结论:

   虚函数表中所指向的函数地址和函数指针所指向的地址都不是该函数的真正入口地址,虚函数表所指向的函数地址是该函数的符号地址;而函数指针所指向的地址则是编译器为了满足函数指针类型定义而生成的函数指针的调用地址。总之,他们是无法进行比较的!(详细请参考:http://blog.csdn.net/happymawolf/article/details/6330073)

  另外关于多个对象是否共用一个虚函数表的问题,通过上面的输出可知:

  c对象和b对象的的虚函数表的地址(对应第一个函数的地址)都是相等的,所以多个对象共用一个虚函数表,但两个对象自身是在不同的内存区域的!

下面是A的子类B的对象的内存分配:



为什么B类的&B::vfun和&A::vfun的地址是一样的呢???求解答!!!




展开阅读全文

没有更多推荐了,返回首页