27.类的大小-灵析社区

菜鸟码转

1.类大小的计算:

说明:类的大小是指类的实例化对象的大小,用 sizeof 对类型名操作时,结果是该类型的对象的大小。计算原则如下:

  • 遵循结构体的成员变量对齐原则。
  • 与普通成员变量有关,与成员函数和静态成员无关。即普通成员函数,静态成员函数,静态数据成员,静态常量数据成员均对类的大小无影响。因为静态数据成员被类的对象共享,并不属于哪个具体的对象。
  • 虚函数对类的大小有影响,是因为虚函数表指针的影响。
  • 虚继承对类的大小有影响,是因为虚基表指针带来的影响。
  • 空类的大小是一个特殊情况,空类的大小为 1,空类同样可以被实例化,而每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以sizeof(A) 的大小为 1。

2.简单情况和空类情况:

C++

/*
说明:程序是在 64 位编译器下测试的
*/
#include <iostream>

using namespace std;

class A
{
private:
    static int s_var; // 不影响类的大小
    const int c_var;  // 4 字节
    int var;          // 8 字节 4 + 4 (int) = 8
    char var1;        // 12 字节 8 + 1 (char) + 3 (填充) = 12
public:
    A(int temp) : c_var(temp) {} // 不影响类的大小
    ~A() {}                    // 不影响类的大小
};

class B
{
};
int main()
{
    A ex1(4);
    B ex2;
    cout << sizeof(ex1) << endl; // 12 字节
    cout << sizeof(ex2) << endl; // 1 字节
    return 0;
}

3.带有虚函数的情况:

注意:虚函数的个数并不影响所占内存的大小,因为类对象的内存中只保存了指向虚函数表的指针。由于不同平台、不同编译器厂商所生成的虚表指针在内存中的布局是不同的,有些将虚表指针置于对象内存中的开头处,有些则置于结尾处。在 X64 GCC 编译器下,虚指针在类的开头出,我们可以通过偏移量获取。

程序示例,我们通过对象内存的开头处取出 vptr,并遍历对象虚函数表。

C++

/*
说明:程序是在 64 位编译器下测试的
*/
#include <iostream>

using namespace std;

class A
{
private:
    static int s_var; // 不影响类的大小
    const int c_var;  // 4 字节
    int var;          // 8 字节 4 + 4 (int) = 8
    char var1;        // 12 字节 8 + 1 (char) + 3 (填充) = 12
public:
    A(int temp) : c_var(temp) {} // 不影响类的大小
    ~A() {}                      // 不影响类的大小
    virtual void f() { cout << "A::f" << endl; }

    virtual void g() { cout << "A::g" << endl; }

    virtual void h() { cout << "A::h" << endl; } // 24 字节 12 + 4 (填充) + 8 (指向虚函数的指针) = 24
};

typedef void (*func)(void);

void printVtable(unsigned long *vptr, int offset) {
	func fn = (func)*((unsigned long*)(*vptr) + offset);
	fn();	
}

int main()
{
    A ex1(4);
    A *p;
    cout << sizeof(p) << endl;   // 8 字节 注意:指针所占的空间和指针指向的数据类型无关
    cout << sizeof(ex1) << endl; // 24 字节
    unsigned long* vPtr = (unsigned long*)(&ex1);
    printVtable(vPtr, 0);
	printVtable(vPtr, 1);
	printVtable(vPtr, 2);
    return 0;
}
/*
8
24
A::f
A::g
A::h
*/

4.含有虚继承的情况:

  • 不包含虚继承的情况,派生类直接继承了基类的成员变量,内存分布如下:

C++

#include <iostream>
using namespace std;

class A
{
public:
     int a;
};

class B : public A
{
public:
    int b;
    void bPrintf() {
    std::cout << "This is class B" << "\n";
    }
};

int main(){
    A a;
    B b;
    cout<<sizeof(a)<<endl;
    cout<<sizeof(b)<<endl;
    return 0;
}
/*
4
8
*/
  • 如果加入虚继承,此时对象中多了一个指向虚基类表的指针,对象 B 与对象 C 均多了一个指针变量 vbptr。

C++

#include <iostream>
using namespace std; // 采用 4 字节对齐

#pragma pack(4)
class A
{
public:
     int a;
};

class B : virtual public A
{
public:
    int b;
    void bPrintf() {
    std::cout << "This is class B" << "\n";}
};

class C : virtual public A
{
public:
    int c;
    void cPrintf() {
    std::cout << "This is class C" << "\n";}
};

class D : public B, public C
{
public:
    int d;
    void dPrintf() {
    std::cout << "This is class D" << "\n";}
};

int main(){
    A a;
    B b;
    C c;
    D d;
    cout<<sizeof(a)<<endl;
    cout<<sizeof(b)<<endl;
    cout<<sizeof(c)<<endl;
    cout<<sizeof(d)<<endl;
    return 0;
}
/*
4
16
16
32
*/

我们可以看到:

实际的内存布局如下:

虚基类表的填充内容如下:

  • 第一项表示派生类对象指针相对于虚基类表指针 vbptr 的偏移,在图中我们可以看到在 B 中,B 的起始地址相对于 vptr 的偏移量为 12;
  • 从第二项开始表示各个基类的地址相对于虚基类表指针 vbptr 的偏移,在图中我们可以看到在 B 中,A 的起始地址相对于 vptr 的偏移量为 12;
  • 虚继承的情况就比较复杂,虚继承需要额外加上一个指向虚基类表的指针。虚继承的基础上如果再加上虚函数,还需要额外加上虚函数表的指针占用的空间。

阅读量:2016

点赞量:0

收藏量:0