LiSheng's blog LiSheng's blog
首页
笔记
个人简历
随笔集
GitHub (opens new window)
首页
笔记
个人简历
随笔集
GitHub (opens new window)
  • golang

  • cplus

    • 内存相关

    • 面向对象

      • 相关问题
      • 构造、析构顺序
      • 对象内存布局
        • 静态、动态多态
        • 虚函数表
        • 重载、重写、覆盖
        • 构造函数不能是虚函数
        • CRTP模式的原理和用途
        • 移动语义move
        • 构造函数初始化列表
        • 静态成员变量和函数
        • 多重继承
        • 菱形问题与虚继承
        • 对象池
      • STL相关

      • 内置数据结构
      • 数据结构示例
      • go和c++对比
      • 关键字

    • leetcode

    • 存储技术

    • 分布式系统

    • 计算机网络

    • Linux操作系统

    • Redis

    • 其他

    • 笔记
    • cplus
    • 面向对象
    lisheng
    2024-09-10
    目录

    对象内存布局

    C++中,类对象的内存布局是一个非常基础但重要的概念,尤其在深入理解对象模型、性能优化和调试时具有很大意义。类对象的内存布局主要包括以下几个部分:

    1. 非静态数据成员:类中的普通成员变量。
    2. 继承相关数据:包括基类的成员和虚基类指针。
    3. 虚函数表指针(vptr):用于支持动态多态性。
    4. 其他可能的内存填充:对齐填充、编译器特性等。

    # 一、基本内存布局

    # 1. 普通类

    对于一个普通的、不使用继承、不含虚函数的类,其内存布局通常只包含非静态数据成员,按照声明顺序依次排列。

    class Simple {
    public:
        int a;
        double b;
        char c;
    };
    
    int main() {
        Simple obj;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    内存布局:

    • a: 4 bytes (int)
    • b: 8 bytes (double)
    • c: 1 byte (char)
    • 可能存在的对齐填充:为了优化内存访问速度,编译器可能会在 c 之后增加3字节的填充,使整个对象对齐到8字节边界。

    # 二、内存对齐

    • 内存对齐:编译器可能会在成员变量之间插入额外的字节(填充字节),以确保每个成员变量都能被高效访问。这些填充字节不一定是显式的,但它们影响对象的内存大小。
    class Align {
    public:
        char c;  // 1 byte
        int i;   // 4 bytes
        double d; // 8 bytes
    };
    
    int main() {
        Align obj;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    内存布局:

    • c: 1 byte (char)
    • 填充: 3 bytes (为了使 i 对齐到4字节边界)
    • i: 4 bytes (int)
    • d: 8 bytes (double)

    # 三、继承下的内存布局

    # 1. 单继承

    在单继承的情况下,派生类对象的内存布局包括基类的成员以及派生类自身的成员。基类的成员会按照基类声明的顺序首先被存储,然后是派生类的成员。

    class Base {
    public:
        int x;
    };
    
    class Derived : public Base {
    public:
        int y;
    };
    
    int main() {
        Derived obj;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    内存布局:

    • Base::x: 4 bytes (int)
    • Derived::y: 4 bytes (int)

    # 2. 多重继承

    在多重继承的情况下,派生类对象的内存布局包含所有基类的成员,按继承顺序依次排列。

    class Base1 {
    public:
        int a;
    };
    
    class Base2 {
    public:
        double b;
    };
    
    class Derived : public Base1, public Base2 {
    public:
        char c;
    };
    
    int main() {
        Derived obj;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    内存布局:

    • Base1::a: 4 bytes (int)
    • Base2::b: 8 bytes (double)
    • Derived::c: 1 byte (char)
    • 可能存在的对齐填充:为了对齐 b,a 之后可能会有4字节的填充。

    # 四、虚函数与虚函数表指针(vptr)

    当类中包含虚函数时,编译器会为该类的每个对象添加一个隐藏的指针 vptr,指向虚函数表(vtable)。

    class Base {
    public:
        virtual void func() {}
        int x;
    };
    
    class Derived : public Base {
    public:
        int y;
    };
    
    int main() {
        Derived obj;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    内存布局:

    • vptr: 8 bytes (在64位系统上)
    • Base::x: 4 bytes (int)
    • Derived::y: 4 bytes (int)
    • 可能存在的对齐填充:为对齐内存,可能会在 vptr 后有填充字节。

    # 五、虚继承

    虚继承用于解决“菱形继承”问题(多继承中,同一个基类通过不同路径多次继承)。虚继承会在对象布局中添加一个指向虚基类的指针。

    class A {
    public:
        int x;
    };
    
    class B : virtual public A {
    public:
        int y;
    };
    
    class C : virtual public A {
    public:
        int z;
    };
    
    class D : public B, public C {
    public:
        int w;
    };
    
    int main() {
        D obj;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    内存布局:

    • vptr(B的虚基类指针): 8 bytes
    • vptr(C的虚基类指针): 8 bytes
    • A::x: 4 bytes
    • B::y: 4 bytes
    • C::z: 4 bytes
    • D::w: 4 bytes

    # 六、对象大小计算

    在考虑类对象的大小时,必须包括所有的非静态数据成员、对齐填充、vptr 和虚基类指针。可以使用 sizeof 操作符计算对象的实际大小。

    std::cout << "Size of Derived: " << sizeof(Derived) << std::endl;
    
    1

    # 总结

    • 非静态数据成员:按声明顺序排列,并受内存对齐影响。
    • 继承:派生类对象包含所有基类的成员。
    • 虚函数表指针:对于包含虚函数的类,每个对象都有一个指向虚函数表的指针 vptr。
    • 虚继承:派生类对象可能包含指向虚基类的指针,用于解决多重继承中的“菱形问题”。
    • 内存对齐:编译器可能插入填充字节来优化内存访问。

    理解类对象的内存布局不仅有助于优化代码性能,还能帮助在调试和理解复杂继承结构时避免潜在问题。

    编辑 (opens new window)
    上次更新: 2024/09/13, 11:59:12
    构造、析构顺序
    静态、动态多态

    ← 构造、析构顺序 静态、动态多态→

    最近更新
    01
    ceph分布式存储-对象存储(RGW)搭建
    10-27
    02
    ceph分布式存储-集群客户端连接
    10-27
    03
    ceph分布式存储-管理crushmap
    10-27
    更多文章>
    Theme by Vdoing
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式