C++对象内存模型

一、概述

在编译时,编译器会对成员函数进行重构,让成员函数非成员化,编译器安排this指针作为成员函数的第一个参数,通过this指针可以找到对象的数据成员。


二、有虚函数单继承实验

1. 测试文件

#include <iostream>
using namespace std;

/* 类大小16B,普通成员函数不占类的内存空间 */
class Base_1 {
public:
    int a;
    int b;
    int c;
    int d;
    int func_add(int x, int y) {
        return x + y;
    }
};

/* 类大小24B,有虚函数就存在虚函数表指针 */
class Base_2 {
public:
    int a;
    int b;
    int c;
    int d;
    virtual int func_add(int x, int y) {
        return x + y;
    }
};

/* 类大小16B,类的静态成员变量不占用类的空间 */
class Base_3 {
public:
    int a;
    int b;
    static int c;
    static int d;
    virtual int func_add(int x, int y) {
        return x + y;
    }
};

int Base_3::c = 0x5566;
int Base_3::d = 0x7788;

typedef int (*func_t)(void *p, int x, int y);

class Son_2 : public Base_2 {
public:
    int e;
    int f;
    virtual int fun_sub(int x, int y) { return y - x; }
};

int main()
{
    Base_1 b_1;
    Base_2 b_2;
    Base_3 b_3;
    Son_2 s_2;

    /* 看每个类和对象的大小 */
    cout << "sizeof(Base_1)=" << sizeof(Base_1) << " sizeof(b_1)=" << sizeof(b_1) << endl; //sizeof(Base_1)=16 sizeof(b_1)=16
    cout << "sizeof(Base_2)=" << sizeof(Base_2) << " sizeof(b_2)=" << sizeof(b_2) << endl; //sizeof(Base_2)=24 sizeof(b_2)=24
    cout << "sizeof(Base_3)=" << sizeof(Base_3) << " sizeof(b_3)=" << sizeof(b_3) << endl; //sizeof(Base_1)=16 sizeof(b_1)=16
    cout << "sizeof(Son_2) ="  << sizeof(Son_2) << " sizeof(s_2)=" << sizeof(s_2) << endl; //sizeof(Son_2) =32 sizeof(s_2)=32

    /* 从基类虚函数表中调函数 */
    unsigned long **t_ptr_1 = (unsigned long **)(*(unsigned long *)&b_2);
    int r_b_2_1 = ((func_t)t_ptr_1[0])(&b_2, 0x11, 0x33);
    cout << hex << "r_b_2_1=0x" << r_b_2_1 << endl; //r_b_2_1=0x44

    /* 看对象成员内存分布 */
    s_2.a = 0x1122;
    s_2.b = 0x3344;
    s_2.c = 0x5566;
    s_2.d = 0x7788;
    s_2.e = 0x99aa;
    s_2.f = 0xbbcc;
    int *p = reinterpret_cast<int *>(&s_2);
    for (int i = 0; i < sizeof(s_2)/sizeof(int); i++) { //前8字节是虚函数表指针,之后按定义次序的父类成员变量,子类成员变量 ########
        cout << hex << "0x" << p[i] << endl;
    }
    
    /* 看地址 */
    cout << hex << "&s_2  =" << &s_2   << endl;
    cout << hex << "&s_2.a=" << &s_2.a << endl;
    cout << hex << "&s_2.b=" << &s_2.b << endl;
    cout << hex << "&s_2.c=" << &s_2.c << endl;
    cout << hex << "&s_2.d=" << &s_2.d << endl;
    cout << hex << "&s_2.e=" << &s_2.e << endl;
    cout << hex << "&s_2.f=" << &s_2.f << endl;

    /* 从子类虚函数表中调函数 */
    unsigned long **t_ptr_2 = (unsigned long **)(*(unsigned long *)&s_2);
    int r_s_2_1 = ((func_t)t_ptr_2[0])(&s_2, 0x11, 0x33);
    int r_s_2_2 = ((func_t)t_ptr_2[1])(&s_2, 0x22, 0x44);
    cout << "r_s_2_1=" << r_s_2_1 << " r_s_2_2=" << r_s_2_2 << endl; //r_s_2_1=44 r_s_2_2=22 先是父类的虚函数,然后才是子类自己的虚函数 ########

    return 0;
}

2. 执行结果:

sunfl@XmartOS01:~/tmp/5.cpp_test/6.mem_layout$ ./pp
sizeof(Base_1)=16 sizeof(b_1)=16
sizeof(Base_2)=24 sizeof(b_2)=24
sizeof(Base_3)=16 sizeof(b_3)=16
sizeof(Son_2) =32 sizeof(s_2)=32
r_b_2_1=0x44
0x7f500cf0
0x5621
0x1122
0x3344
0x5566
0x7788
0x99aa
0xbbcc
&s_2  =0x7ffd2687de50
&s_2.a=0x7ffd2687de58
&s_2.b=0x7ffd2687de5c
&s_2.c=0x7ffd2687de60
&s_2.d=0x7ffd2687de64
&s_2.e=0x7ffd2687de68
&s_2.f=0x7ffd2687de6c
r_s_2_1=44 r_s_2_2=22


三、有虚函数多继承实验

1. 测试文件

#include <iostream>
using namespace std;

class Base_1 {
public:
   int a;
   int b;
   virtual int fun_1(int x, int y) { return x + y; }
};

class Base_2 {
public:
   int c;
   int d;
   virtual int fun_2(int x, int y) { return 2 * (x + y); }
};

class Son : public Base_1, public Base_2 {
public:
    int e;
    int f;
    virtual int fun_sub(int x, int y) { return y - x; }
};

typedef int (*func_t)(void *t, int x, int y);

int main()
{
    Son s_1;
    s_1.a = 0x1122;
    s_1.b = 0x3344;
    s_1.c = 0x5566;
    s_1.d = 0x7788;
    s_1.e = 0x99aa;
    s_1.f = 0xbbcc;

    /* 类大小 */
    cout << "sizeof(Base_1)=" << sizeof(Base_1) << " sizeof(Son)=" << sizeof(Son) << " sizeof(s_1)=" << sizeof(s_1) << endl;

    /* 类成员分布 */
    int *p = reinterpret_cast<int *>(&s_1);
    for (int i = 0; i < sizeof(s_1)/sizeof(int); i++) {
        cout << hex << "0x" << p[i] << endl;
    }

    /* 类成员地址 */
    cout << hex << "&s_1  =" << &s_1   << endl;
    cout << hex << "&s_1.a=" << &s_1.a << endl;
    cout << hex << "&s_1.b=" << &s_1.b << endl;
    cout << hex << "&s_1.c=" << &s_1.c << endl;
    cout << hex << "&s_1.d=" << &s_1.d << endl;
    cout << hex << "&s_1.e=" << &s_1.e << endl;
    cout << hex << "&s_1.f=" << &s_1.f << endl;

    /* 第一个虚函数表中的函数 */
    unsigned long **ptr = (unsigned long **)(*(unsigned long *)&s_1);
    int r_1 = ((func_t)ptr[0])(&s_1, 0x11, 0x66);
    int r_2 = ((func_t)ptr[1])(&s_1, 0x33, 0x44);
    cout << "r1=" << r_1 << " r_2=" << r_2 << endl;


#if 0
    /* 不成功,从地址上看,第二个vptr占12B而不是8B */
    unsigned long **ptr_1 = (unsigned long **)(*(unsigned long *)((char*)&s_1 + sizeof(Base_1)));
    int r2_1 = ((func_t)ptr_1[0])(&s_1, 0x11, 0x66);
    int r2_2 = ((func_t)ptr_1[1])(&s_1, 0x33, 0x44);
    cout << "r2_1=" << r2_1 << " r2_2=" << r2_2 << endl;
#endif
}

2. 执行结果:

sizeof(Base_1)=16 sizeof(Son)=40 sizeof(s_1)=40
0xa93cdcb8 //第一个虚函数表指针
0x55df
0x1122
0x3344
0xa93cdcd8 //第二个虚函数表指针
0x55df
0x5566
0x7788
0x99aa
0xbbcc
&s_1  =0x7ffed25273e0
&s_1.a=0x7ffed25273e8
&s_1.b=0x7ffed25273ec
&s_1.c=0x7ffed25273f8 //和上面相差12字节而不是8字节?
&s_1.d=0x7ffed25273fc
&s_1.e=0x7ffed2527400
&s_1.f=0x7ffed2527404
r1=77 r_2=11


三、总结

1. 在没有虚函数的情况下,相较于C,C++没有消耗额外的内存,在有虚函数的情况下,类中会多出一个虚函数表指针。

2. 类的成员函数所有对象共享,在内存中只有一份。

3. 类的static成员变量存储上不属于类也不属于类对象,在类和对象的大小中看不到它。

4. 对于继承,按定义次序,先是父类成员变量,之后才是子类成员变量。

5. 对于继承,虚函数表中先是父类的虚函数,然后才是子类的虚函数。

6. 对于多继承,按继承的从左到右的次序依次排布继承得来的成员。

7. 对于多继承,父类都有虚函数的,子类中有多个虚函数表指针。

 


参考:
C++对象内存模型: https://tangocc.github.io/2018/03/20/cpp-class-memory-struct/

 

posted on 2024-01-29 17:13  Hello-World3  阅读(42)  评论(0编辑  收藏  举报

导航