MST

星途 面试题库

面试题:C++类的内存布局与对象模型深度剖析

深入分析C++类的内存布局,包括数据成员、成员函数、虚函数表等在内存中的分布情况。解释不同继承方式(公有继承、私有继承、保护继承)对类的内存布局有何影响,以及在多重继承和虚继承场景下,类的对象模型是怎样的。
28.7万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

C++类的内存布局

  1. 数据成员
    • 非静态数据成员:类的对象中包含非静态数据成员,它们按照声明顺序依次存储在对象的内存空间中。例如:
class A {
    int a;
    double b;
};

在这个类A的对象中,int类型的a先存储,然后是double类型的b。对象的大小至少为sizeof(int)+sizeof(double),通常还会考虑内存对齐,可能会更大。

  • 静态数据成员:静态数据成员不包含在类的对象中,它们存储在全局数据区,属于整个类,而不是某个具体对象。所有对象共享静态数据成员。例如:
class B {
    static int s;
};

B::s存储在全局数据区,不影响B类对象的大小。 2. 成员函数

  • 普通成员函数:普通成员函数代码存储在代码段,并不占用类对象的内存空间。普通成员函数通过this指针访问类的非静态数据成员。例如:
class C {
    int data;
public:
    void func() {
        // 通过this指针访问data
        this->data = 10;
    }
};

C类的对象大小只由data决定,func函数代码存储在代码段。

  • 虚函数:
    • 虚函数表(vtable):当类中包含虚函数时,类的对象会包含一个指向虚函数表的指针(vptr)。虚函数表是一个存储虚函数地址的数组,位于只读数据段。例如:
class D {
public:
    virtual void vfunc() {}
};

D类对象中包含一个vptr,其大小通常为指针大小(如在64位系统中为8字节)。虚函数表中存储了vfunc的地址。如果D类有多个虚函数,虚函数表中会按顺序存储这些虚函数的地址。 - 动态绑定:当通过基类指针或引用调用虚函数时,会根据对象实际类型对应的虚函数表来调用正确的虚函数,实现动态绑定。

不同继承方式对内存布局的影响

  1. 公有继承
    • 公有继承保持基类成员的访问属性。在内存布局上,派生类对象包含基类对象的所有非静态数据成员,且在派生类对象内存空间中,基类对象部分先存储,然后是派生类自己的非静态数据成员。例如:
class Base {
    int a;
public:
    virtual void vfunc() {}
};
class Derived : public Base {
    double b;
public:
    void newfunc() {}
};

Derived类对象内存布局中,先存储Base类对象部分(包括vptra),然后是Derived类自己的bDerived类对象可以访问Base类的公有和保护成员。 2. 私有继承

  • 私有继承将基类的公有和保护成员变为派生类的私有成员。在内存布局上,与公有继承类似,派生类对象先存储基类对象部分,然后是派生类自己的非静态数据成员。但派生类的外部代码无法访问从基类继承的公有和保护成员,只有派生类内部成员函数可以访问。
  1. 保护继承
    • 保护继承将基类的公有成员变为派生类的保护成员。内存布局同样是派生类对象先存储基类对象部分,再存储自己的非静态数据成员。派生类的外部代码无法访问从基类继承的公有成员,派生类的派生类可以访问这些保护成员。

多重继承和虚继承场景下的对象模型

  1. 多重继承
    • 当一个类从多个基类继承时,派生类对象内存布局包含多个基类对象部分。例如:
class Base1 {
    int a;
};
class Base2 {
    double b;
};
class Derived : public Base1, public Base2 {
    char c;
};

Derived类对象内存布局中,先存储Base1类对象部分(包含a),然后是Base2类对象部分(包含b),最后是Derived类自己的c。这种情况下可能会出现菱形继承问题(多个基类继承自同一个基类,导致数据冗余和歧义)。 2. 虚继承

  • 虚继承用于解决菱形继承问题。在虚继承中,虚基类的成员在派生类对象中只存储一份。例如:
class Base {
    int data;
};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class Final : public Derived1, public Derived2 {};

Final类对象内存布局中,Base类的data只存储一份。派生类对象会包含一个指向虚基类偏移量表的指针(vbptr),通过这个表可以找到虚基类成员在对象中的位置,避免数据冗余。