文章

C++ sizeof

C++ sizeof

空类的大小为1个字节

C++标准规定,任何两个不同的对象都必须在内存中拥有不同的地址。

如果一个空类的大小为0,那么当你创建一个空类的数组时,所有元素的地址都会相同,这就无法区分它们了。为了保证对象实例的唯一性,编译器会为空类分配一个字节的最小内存空间。

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
class A{};

int main() {
	cout << sizeof(A) << endl;	// 1
	return 0;
}

一个类中,虚函数本身、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间

  • 非静态成员函数、虚函数 属于“行为”,存放在代码区,不占对象空间。
  • 静态数据成员属于类而非对象,存放在静态存储区,不占对象空间。
  • 非静态成员变量才会真正占对象内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 静态数据成员被编译器放在程序的一个global data members中,它是类的一个数据成员,但不影响类的大小。不管这个类产生了多少个实例,还是派生了多少新的类,静态数据成员只有一个实例。静态数据成员,一旦被声明,就已经存在。 
#include<iostream>
using namespace std;
class A
{
    public:
        char b;
        virtual void fun() {};
        static int c;
        static int d;
        static int f;
};

class B
{
    public:
        virtual void fun() {};
        static int c;
        static int d;
        static int f;
};

int main()
{
    cout<<sizeof(A)<<endl; 	// 输出16
    cout<<sizeof(B)<<endl;	// 输出8
    return 0;
}

对于包含虚函数的类,不管有多少个虚函数,只有一个虚指针vptr的大小

  • 单继承无继承的情况下,一个类无论有多少个虚函数,编译器都会创建一个虚函数表vtable来存放虚函数的地址,因此只需要一个虚指针vptr指向vtable即可
  • 多重继承的情况下,如果多个基类都含有虚函数,那么派生类对象为了维护每个基类子对象的独立性,可能会有多个vptr
1
2
3
4
5
6
class Base1 { virtual void func1(); };
class Base2 { virtual void func2(); };
class Derived : public Base1, public Base2 {};
// 在这种情况下,一个 Derived 对象内部通常会有两个 vptr,
// 一个指向 Base1 相关的 vtable,一个指向 Base2 相关的 vtable。
// 所以 sizeof(Derived) 会比 sizeof(Base1) + sizeof(Base2) 更大。
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
class A{
    virtual void fun();
    virtual void fun1();
    virtual void fun2();
    virtual void fun3();
};
int main()
{
    cout<<sizeof(A)<<endl; // 8
    return 0;
}

普通继承、派生类继承了所有基类的函数与成员,要按照字节对齐来计算大小

  • 普通继承时,派生类会包含基类的非静态成员,布局上要考虑字节对齐规则
  • 成员函数依旧不占用对象空间

sizeof(Derived) 通常大于等于 sizeof(Base) + sizeof(派生类新增成员)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<iostream>

using namespace std;

class A
{
    public:
        char a;
        int b;
};

class B:A
{
    public:
        short a;
        long b;
};

class C
{
	A a;
	char c;
};

class D
{
    public:
        char a;
    private:
        int b;
};

class E:D
{
    public:
        short a;
    // private:
        long b;
};


// 此时B按照顺序声明 char a; int b; short a; long b;
// 根据字节对其 4+4=8, 8+8+8=24

int main() {
	cout << sizeof(A) << endl;	// 8
	cout << sizeof(B) << endl;	// 24
	cout << sizeof(C) << endl;	// 12
	cout << sizeof(D) << endl;  // 8
	cout << sizeof(E) << endl;  // 24
	return 0;
}

虚函数继承,不管是单继承还是多继承,都是继承了基类的vptr

派生类会构建自己的虚函数表(vtable),并且其对象拥有自己的虚函数指针(vptr)。

  • 派生类的vtable是基于基类vtable构建的。如果派生类重写了基类的某个虚函数,那么派生类vtable中对应的条目就会更新为派生类重写后的函数地址。

  • 派生类的对象中的vptr会指向这个属于派生类自己的vtable

  • 所以,派生类并不是直接“继承”或“复制”了基类的vptr,而是拥有了一个指向自己vtablevptr,这个vtable在结构上与基类的vtable有关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>

using namespace std;

class A
{
	virtual void fun(){}
};

class C:public A
{

};

int main() {
	cout << sizeof(C) << endl;	// 8
	return 0;
}

虚函数、虚继承

  • 虚函数 (Virtual Function):目的是实现运行时多态。其实现机制是虚函数指针 (vptr) 和虚函数表 (vtable)。

  • 虚继承 (Virtual Inheritance):目的是在多重继承中解决“菱形继承”问题,确保共同的基类在派生类中只存在一个实例。其实现机制通常是虚基类指针 (vbptr) 或虚基类表。这个指针指向一个偏移量表,用于在运行时查找虚基类子对象的位置。

一个类可以有虚继承而没有虚函数,也可以有虚函数而没有虚继承。如果一个类同时使用了虚继承并且基类中有虚函数,那么这个类的对象可能会同时包含 vptrvbptr,会变得更加复杂。

本文由作者按照 CC BY 4.0 进行授权