前言:仅以本文献给C++初学者,高手请绕道。测试环境VS2013
PS:在VS2013中,debug时,CC CC CC CC 4个字节是栈中对象的分割线
一、单重继承不带虚函数的类对象内存结构
测试代码如下:
class Parent{
public:
Parent() :pdata(0x11111111){} //将父类的pdata初始化为0x11111111
private:
int pdata;
};
class Son : public Parent{
public:
Son():sdata(0x22222222){} //将子类的sdata初始化为0x22222222
private:
int sdata;
};
int main(int argc, char** argv){
Son son;
return 0; //在此行设置断点,观察son的内存结构
}
通过调试观察son的内存,可以看到在简单的单重继承不带虚函数的应用场景中,子类对象所占用的字节数,等于各成员占有字节数之和。
在我们这个测试场景中占8个字节 = sizeof(sdata) + sizeof(pdata)
二、单重继承带虚函数的类对象内存结构
class Parent{
public:
Parent() :pdata(0x11111111){}
virtual void ParentVirtualFunction(void){} //虚函数
private:
int pdata;
};
class Son : public Parent{
public:
Son():sdata(0x22222222){}
private:
int sdata;
};
int main(int argc, char** argv){
Parent parent;
Son son;
return 0; //在此行设置断点,观察两个对象的内存状况
}
a、我们先看看parent的内存状况
parent的地址是:0x0016f984,这个地址所执向的内存包含两个东西:一个虚表指针,一个pdata数据。因为Parent类含有虚函数,所以parent对象的前四个字节存放的
是虚表的地址,我们可以看到虚表地址是:0x00b8cd20。关于虚表在这里不做讨论。
接着我们看下parent对象的内存布局吧:
通过上面的截图,我们看到parent地址所指向的8个字节的前4个字节就是虚表指针,后4个字节是pdata。整个对象占用8个字节
b、现在我们来看看son的内存状况
观察上面的截图,可以看出son由两部分组成:一部分是从父类继承过来的数据,还有一部分是自己本身的数据
接着我们看下物理内存:
物理内存大小占12个字节,前4个字节是虚表指针,后8个字节是数据成员
3.单重虚继承不带虚函数的类对象内存结构
class Parent{
public:
Parent() :pdata(0x11111111){}
void ParentVirtualFunction(void){}
private:
int pdata;
};
class Son : virtual public Parent{
public:
Son():sdata(0x22222222){}
private:
int sdata;
};
int main(int argc, char** argv){
Parent parent;
Son son;
Parent* p1 = &son;
Son* p2 = &son;
return 0;
}
首先看下图,请注意虚继承时,p1 和 p2的值是不一样的。p1的类型是Parent*,在虚继承时,将子类对象的地址
赋值给父类指针,编译器会将子类对象中属于父类的那部分成员的起始地址赋值给父类指针。所以我们看到p1的值不等于
p2的值。
观察下图,我们可以看到p1指向11 11 11 11 (pdata)
再次观察上面的截图,可以发现除了sdata,pdata,还有4个字节00 41 cc 70,这4个字节存放的是一个特殊结构体的指针,这个特殊结构体的类型如下:
struct TagOffset {
char unKnow[4];
int nOffset;
};
这结构体中nOffset字段值表示父类成员在子类对象中的偏移量。在本例中这个结构体指针的值是:00 41 cc 70,下面我们看下这个指针指向的内存:
上周中后四个字节就是nOffset, 值等于8, 表示分类成员在子对象中的偏移量是8:如下图:
11 11 11 11 在子类对象中的偏移量是8
4、单重虚继承带虚函数的类对象内存结构
class Parent{
public:
Parent() :pdata(0x11111111){}
virtual void ParentVirtualFunction(void){}
private:
int pdata;
};
class Son : virtual public Parent{ //子类没有重写父类的虚函数,如果重写内存布局会稍有不同
public:
Son():sdata(0x22222222){}
private:
int sdata;
};
int main(int argc, char** argv){
Parent parent;
Son son;
Parent* p1 = &son;
Son* p2 = &son;
return 0;
}
和第三种情况相比,因为父类带有虚函数,所以子类存在虚表,74 CC 41 00 就是虚表指针。