C++中类所占的空间

类所占的空间

概览

struct A
{
   public:
};

std::cout<<"sizeof(A):"<<sizeof(A)<<std::endl; // sizeof(A):1

A是一个空类,如果直接输出A所占的字节会发现只有1个字节。可见对象至少占一个字节。

struct B
{
   public:
   void func(){};
   void func1(){};
};

struct C{
   //char variable;
   int x;
};

std::cout<<"sizeof(B):"<<sizeof(B)<<std::endl; //  sizeof(B):1
std::cout<<"sizeof(C):"<<sizeof(C)<<std::endl; //  sizeof(C):4

B只含有两个成员函数,依然只占1个字节。成员函数不占用类对象本身的内存空间。而C会占用4个字节。可见:每个对象所占用的存储空间只是该对象的数据部分所占用的存储空间,而不包括函数代码所占用的存储空间

对象结构小总结

1、 非静态的成员变量跟着类对象走(存在对象内部),换言之,每个类对象都有自己的成员变量。

2、 静态成员变量不会保存在对象内部。

struct D
{
   static int x;
   static int y;
};
std::cout<<"sizeof(D)"<<std::endl; // sizeof(D):1

类D所占字节也依旧是1个字节。

3、无论是静态还是非静态成员函数,都保存在对象之外, 不占用类对象的内存空间。

4、如果类内部有虚函数(无论有多少个虚函数所占空间一致),其会占用对象内部一部分内存空间。具体占用大小由是64位机还是32位机来定义。本人在Fedora36上测试,如果类里含有虚函数,对象会额外多出8个字节。

struct E
{
   virtual void func(){};
   virtual void func1(){};
};
std::cout<<"sizeof(E):"<<sizeof(E)<<std::endl; // sizeof(E):8

之所以会多出8个字节,是因为对象内部会多出一个指向虚函数表的指针(在64位机上,void*指针会占用8个字节)。

5、 如果有多个数据成员,

struct F
{
   int x;
   char y;
};
std::cout<<"sizeof(F):"<<sizeof(F)<<std::endl; // sizeof(F):8

虽然数据成员x和y加起来总共有5个字节,但是为了进行内存对齐,实际F的对象占用8个字节。

this指针调整

我们考虑以下多重继承的情况:

struct A
{
    int x;
    A():x(0)
    {
        std::cout<<"A() this:"<<this<<std::endl;
    }
    A(int x):x(x)
    {
        std::cout<<"A() this:"<<this<<std::endl;
    }
    void print_athis()
    {
        std::cout<<"print_athis() ";
        std::cout<<"this:"<<this<<std::endl;
    }
};

struct B
{
    int x;
    B() :x(0)
    {
        std::cout<<"B() this:"<<this<<std::endl;
    }
    B(int x):x(x)
    {
        std::cout<<"B() this:"<<this<<std::endl;
    }
    void print_bthis()
    {
        std::cout<<"print_bthis() ";
        std::cout<<"this:"<<this<<std::endl;
    }
};

struct C: public A, public B
{
    int x;
    C():x(0)
    {
        std::cout<<"C() this:"<<this<<std::endl;
    }
    C(int x):x(x)
    {
        std::cout<<"C() this:"<<this<<std::endl;
    }
    void print_cthis()
    {
        std::cout<<"print_cthis() ";
        std::cout<<"this:"<<this<<std::endl;
    }
};

接着我们分别调用各自的成员函数,看看各自的this指针地址。

C myc{};
myc.print_athis();
myc.print_bthis();
myc.print_cthis();

输出:

A() this:0x7ffc2d763a44
B() this:0x7ffc2d763a48
C() this:0x7ffc2d763a44
print_athis() this:0x7ffc2d763a44
print_bthis() this:0x7ffc2d763a48
print_cthis() this:0x7ffc2d763a44

我们会发现,类A会与类C共享同一个地址。
这里,我们可以得到结论:派生类是包含基类子对象的。且二者指向的地址相同。如果出现多重继承的情况,那么只有第一个基类子对象与派生类地址相同。后续的基类子对象会根据前面继承的基类子对象的大小决定其地址。

如果我们在类C中也覆盖print_bthis()函数,那么就会输出print_bthis() this:0x7ffc2d763a44