上一篇博客《C++对象模型:g++实现(一)》用我的理解总结了在无继承体系下g++实现的C++对象的内存布局,这篇就来总结一下在有继承情况下的C++对象的内存布局。
本文中所有代码均可从这里拿到(百度网盘)。
1. 有继承情况下的C++对象的内存布局
C++是支持多继承的,而多继承可能就会出现继承的两个(或多个)类有公共的父类,为了防止在一个对象中出现多个公共父类的实体,C++就提出了虚继承。因此总的来说C++的继承有两种:无虚继承和有虚继承。
1.1 无虚继承
在无虚继承情况下,无论是public
继承还是protected
、private
继承,其内存布局都是一样的,因此这里只提供public
继承的例子。
1.1.1 所有的基类和派生类都没有虚函数
在这种情况下,其实就相当于构造了一个对象,其内部按基类的声明顺序添加了各个基类对象,最后再添加派生类自己的对象,其对齐要求和类作为(非静态)成员变量是一样的。
// test09.cpp
#include <cstdio>
class Base1 {
public:
Base1(int i, char c)
:m_i(i), m_c(c)
{}
private: // 在Deriverd中:
int m_i; // Offset: 0
char m_c; // Offset: 4
}; // size: 8
class Base2{
public:
Base2(short s, int i)
: m_s(s), m_i(i) {}
private: // 在Deriverd中:
short m_s; // Offset: 8
int m_i; // Offset: 10
}; // size: 8
class Derived: public Base1, public Base2 {
public:
Derived(int i1, char c, short s, int i2, long l)
: Base1(i1, c), Base2(s, i2), m_l(l)
{}
long getLong() const { return m_l; }
private:
long m_l; // Offset: 16
}; // size: 8 + 8 + 8 = 24
int main() {
std::printf("sizeof Derived: %d\n", static_cast<int>(sizeof(Derived)));
Derived d(1, static_cast<char>(2), static_cast<short>(3), 4, static_cast<long>(5));
long l = d.getLong();
}
// Output:
// sizeof Derived: 24
使用gdb调试,查看内存布局,如下:
如果不是按基类整体作为一个(非静态)成员变量的话,Base2::m_s
的offset应为6,但实际上其offset为8,因为Base2
的对齐要求为4。
1.1.2 基类无虚函数,派生类有虚函数
这个也很简单,只需要在整个类的头部安插一个虚表指针vptr
即可。
// test10.cpp
// 只是令Derived::getLong()为虚函数
#include <cstdio>
class Base1 {
public:
Base1(int i, char c)
:m_i(i), m_c(c)
{}
private: // 在Deriverd中:
int m_i; // Offset: 0
char m_c; // Offset: 4
}; // size: 8
class Base2{
public:
Base2(short s, int i)
: m_s(s), m_i(i) {}
private: // 在Deriverd中:
short m_s; // Offset: 8
int m_i; // Offset: 10
}; // size: 8
class Derived: public Base1, public Base2 {
public:
Derived(int i1, char c, short s, int i2, long l)
: Base1(i1, c), Base2(s, i2), m_l(l)
{}
virtual // 仅在此有改动
long getLong() const { return m_l; }
private:
long m_l; // Offset: 16
}; // size: 8 + 8 + 8 = 24
int main() {
std::printf("sizeof Derived: %d\n", static_cast<int>(sizeof(Derived)));
Derived d(1, static_cast<char>(2), static_cast<short>(3), 4, static_cast<long>(5));
long l = d.getLong();
}
// Output:
// sizeof Derived: 32
1.1.3 基类也有虚函数
在这种情况下,派生类会继承基类的虚函数,也会复用基类放置虚表指针的内存位置,无论基类有没有定义虚函数,有没有新增虚函数,都不会增加新的虚表指针,而是复用基类的虚表指针的内存位置。
// test11.cpp
// 添加了:
// virtual char Base1::getChar();
// int Base1::getIntOfBase1();
// virtual char Base2::getShort();
// int Base2::getIntOfBase2();
// virtual int Derived::getInt();
#include <cstdio>
class Base1 {
public:
Base1(int i, char c)
:m_i(i), m_c(c)
{}
virtual
char getChar() const { return m_c; } // ! 添加
int getIntOfBase1() const { return m_i; } // ! 添加
private:
int m_i;
char m_c;
};
class Base2{
public:
Base2(short s, int i)
: m_s(s), m_i(i) {}
virtual
short getShort() const { return m_s; } // ! 添加
int getIntOfBase2() const { return m_i; } // ! 添加
private:
short m_s;
int m_i;
};
class Derived: public Base1, public Base2 {
public:
Derived(int i1, char c, short s, int i2, long l)
: Base1(i1, c), Base2(s, i2), m_l(l)
{}
virtual
long getLong() const { return m_l; }
virtual
int getInt() { return getIntOfBase2(); } // ! 添加
private:
long m_l;
};
int main() {
std::printf("sizeof Derived: %d\n&q