zoukankan      html  css  js  c++  java
  • C++对象内存布局

    引言

    C++使用虚函数来实现多态机制,大多数编译器是通过虚函数表来实现动态绑定。

    类的内存布局

    1.普通类

    class B {
    public:
      int m;
      int n;
    };
    
    int main() {
      printf("%ld
    ", sizeof(B));
    
      B b;
      printf("%p, %p, %p
    ", &b, &b.m, &b.n);
    
      return 0;
    }
    

    类中只有普通成员变量,对象在内存中顺序存储成员变量。输出:

    8
    0x7ffee48dae00, 0x7ffee48dae00, 0x7ffee48dae04
    

    2.有虚函数的类

    class B {
    public:
      virtual void f() {
        printf("B::f
    ");
      }
      virtual void g() {
        printf("B::g
    ");
      }
      virtual void h() {
        printf("B::h
    ");
      }
    
      int m;
      int n;
    };
    
    int main() {
      printf("%ld
    ", sizeof(B));
    
      B b;
      printf("%p
    %p
    %p
    ", &b, &b.m, &b.n);
    
      return 0;
    }
    

    先看输出结果:

    16
    0x7ffea995e540
    0x7ffea995e548
    0x7ffea995e54c
    

    我们看到,这个对象的内存占用比上一个多了8个字节,其中成员变量m的地址也和对象b的地址不一样了,正好是 &b+8。
    之所以这样,是因为对象中多出了虚函数指针,这个虚函数指针指向这个类的虚函数表,也就是说,这个指针保存的内容是一个虚函数表的地址,虚函数表,在内存中就是一个存储函数指针的数组

    #include <stdio.h>
    
    class B {
    public:
      virtual void f() {
        printf("B::f
    ");
      }
      virtual void g() {
        printf("B::g
    ");
      }
      virtual void h() {
        printf("B::h
    ");
      }
    
      int m;
      int n;
    };
    
    using Func = void(*)(void);
    
    int main() {
      printf("%ld
    ", sizeof(B));
    
      B b;
      printf("%p
    %p
    %p
    ", &b, &b.m, &b.n);
    
      long** vt = (long**)*(long***)&b;
      printf("vtable addr: %lx
    ", (long)vt);
    
      Func p = nullptr;
      for (int i = 0; i < 3; ++i)
      {
        p = (Func)*(vt + i);
        p();
      }
    
      return 0;
    }
    

    输出如下:

    16
    0x7ffe4222ebd0
    0x7ffe4222ebd8
    0x7ffe4222ebdc
    vtable addr: 55a2e85c3d80
    B::f
    B::g
    B::h
    

    图形表示为:

    3.单继承无重写的类

    写一个子类,继承自B类,没有重写B中的方法

    #include <stdio.h>
    
    class B {
    public:
      virtual void f() {
        printf("B::f
    ");
      }
      virtual void g() {
        printf("B::g
    ");
      }
      virtual void h() {
        printf("B::h
    ");
      }
    
      int m;
      int n;
    };
    
    class D : public B {
    public:
      virtual void df() {
        printf("D::df
    ");
      }
      virtual void dg() {
        printf("D::dg
    ");
      }
      virtual void dh() {
        printf("D::dh
    ");
      }
    
      int x,y;
    };
    
    using Func = void(*)(void);
    
    int main() {
      B b;
      printf("sizeof b: %ld
    ", sizeof(b));
      printf("addr B:
    %p
    %p
    %p
    ", &b, &b.m, &b.n);
      printf("vtable B: %p
    ", *(long***)&b);
      printf("------------
    ");
    
      D d;
      printf("sizeof d:%ld
    ", sizeof(d));
      printf("addr D:
    %p
    %p
    %p
    %p
    %p
    ", &d, &d.m, &d.n, &d.x, &d.y);
      printf("vtable D: %p
    ", *(long***)&d);
      printf("------------
    ");
    
      long** vt = (long**)*(long***)&d;
      Func p = nullptr;
      for (int i = 0; i < 6; ++i)
      {
        p = (Func)*(vt+i);
        p();
      }
    
      return 0;
    }
    

    结果:

    sizeof b: 16
    addr B:
    0x7fffce329990
    0x7fffce329998
    0x7fffce32999c
    vtable B: 0x559e5a8c9d68
    ------------
    sizeof d:24
    addr D:
    0x7fffce3299a0
    0x7fffce3299a8
    0x7fffce3299ac
    0x7fffce3299b0
    0x7fffce3299b4
    vtable D: 0x559e5a8c9d28
    ------------
    B::f
    B::g
    B::h
    D::df
    D::dg
    D::dh
    

    这个时候内存布局变成了这样:

    可以看到,子类只有一个虚函数指针,一个虚函数表,子类的函数附加在基类后面

    4.单继承有重写

    #include <stdio.h>
    
    class B {
    public:
      virtual void f() {
        printf("B::f
    ");
      }
      virtual void g() {
        printf("B::g
    ");
      }
      virtual void h() {
        printf("B::h
    ");
      }
    
      int m;
      int n;
    };
    
    class D : public B {
    public:
      virtual void f() {
        printf("D::f
    ");
      }
      virtual void dg() {
        printf("D::dg
    ");
      }
      virtual void dh() {
        printf("D::dh
    ");
      }
    
      int x,y;
    };
    
    using Func = void(*)(void);
    
    int main() {
      B b;
      printf("sizeof b: %ld
    ", sizeof(b));
      printf("addr B:
    %p
    %p
    %p
    ", &b, &b.m, &b.n);
      printf("vtable B: %p
    ", *(long***)&b);
      printf("------------
    ");
    
      D d;
      printf("sizeof d:%ld
    ", sizeof(d));
      printf("addr D:
    %p
    %p
    %p
    %p
    %p
    ", &d, &d.m, &d.n, &d.x, &d.y);
      printf("vtable D: %p
    ", *(long***)&d);
      printf("------------
    ");
    
      long** vt = (long**)*(long***)&d;
      Func p = nullptr;
      for (int i = 0; i < 5; ++i)
      {
        p = (Func)*(vt+i);
        p();
      }
    
      return 0;
    }
    

    结果:

    sizeof b: 16
    addr B:
    0x7fffbfd06b60
    0x7fffbfd06b68
    0x7fffbfd06b6c
    vtable B: 0x56277e20cd68
    ------------
    sizeof d:24
    addr D:
    0x7fffbfd06b70
    0x7fffbfd06b78
    0x7fffbfd06b7c
    0x7fffbfd06b80
    0x7fffbfd06b84
    vtable D: 0x56277e20cd30
    ------------
    D::f
    B::g
    B::h
    D::dg
    D::dh
    

    子类重写了基类的 B::f 方法,内存布局变为:

    类D的虚函数表中,第一个函数被替换成了 D::f,其他不变。
    这个替换,是在编译期间完成的。
    这里就是实现多态的关键地方了,当用基类的指针调用f函数,如果该指针指向的是基类,那么虚函数指针指向的就是基类的虚函数表,调用B::f。如果该指针指向子类对象,则虚函数指针指向子类的虚函数表,调用D::f

    5.多重继承无重写

    #include <stdio.h>
    
    class B {
    public:
      virtual void f() {
        printf("B::f
    ");
      }
      virtual void g() {
        printf("B::g
    ");
      }
      virtual void h() {
        printf("B::h
    ");
      }
    
      int m;
      int n;
    };
    
    class B2 {
    public:
      virtual void f() {
        printf("B2::f
    ");
      }
      virtual void g() {
        printf("B2::g
    ");
      }
      virtual void h() {
        printf("B2::h
    ");
      }
    
      int i,j;
    };
    
    class D : public B, public B2 {
    public:
      virtual void df() {
        printf("D::df
    ");
      }
      virtual void dg() {
        printf("D::dg
    ");
      }
      virtual void dh() {
        printf("D::dh
    ");
      }
    
      int x,y;
    };
    
    using Func = void(*)(void);
    
    int main() {
      B b;
      printf("sizeof b: %ld
    ", sizeof(b));
      printf("addr B:
    %p
    %p
    %p
    ", &b, &b.m, &b.n);
      printf("vtable B: %p
    ", *(long***)&b);
      printf("------------
    ");
    
      B2 b2;
      printf("sizeof b2: %ld
    ", sizeof(b2));
      printf("addr B2:
    %p
    %p
    %p
    ", &b2, &b2.i, &b2.j);
      printf("vtable B2: %p
    ", *(long***)&b2);
      printf("------------
    ");
    
      D d;
      printf("sizeof d:%ld
    ", sizeof(d));
      printf("addr D:
    %p
    %p
    %p
    %p
    %p
    %p
    %p
    ", &d, &d.m, &d.n, &d.i, &d.j, &d.x, &d.y);
      printf("vtable D: %p
    ", *(long***)&d);
      printf("------------
    ");
    
      // first vtable pointer
      long** vt1 = (long**)*(long***)&d;
      Func p = nullptr;
      for (int i = 0; i < 6; ++i)
      {
        p = (Func)*(vt1+i);
        p();
      }
    
      // second vtable pointer. member 'm' and 'n' type is int, pointer +2
      long** vt2 = (long**)*((long***)&d + 2);
      for (int i = 0; i < 3; ++i)
      {
        p = (Func)*(vt2+i);
        p();
      }
    
      return 0;
    }
    

    结果:

    sizeof b: 16
    addr B:
    0x7ffc2868cf40
    0x7ffc2868cf48
    0x7ffc2868cf4c
    vtable B: 0x5574802e5d38
    ------------
    sizeof b2: 16
    addr B2:
    0x7ffc2868cf50
    0x7ffc2868cf58
    0x7ffc2868cf5c
    vtable B2: 0x5574802e5d10
    ------------
    sizeof d:40
    addr D:
    0x7ffc2868cf60
    0x7ffc2868cf68
    0x7ffc2868cf6c
    0x7ffc2868cf78
    0x7ffc2868cf7c
    0x7ffc2868cf80
    0x7ffc2868cf84
    vtable D: 0x5574802e5ca8
    ------------
    B::f
    B::g
    B::h
    D::df
    D::dg
    D::dh
    B2::f
    B2::g
    B2::h
    

    这次的内存布局为:

    可以看到:
    每个基类都有虚函数表;子类的函数被放到第一个第一个基类的虚函数表中。

    6.多重继承有重写

    #include <stdio.h>
    
    class B {
    public:
      virtual void f() {
        printf("B::f
    ");
      }
      virtual void g() {
        printf("B::g
    ");
      }
      virtual void h() {
        printf("B::h
    ");
      }
    
      int m;
      int n;
    };
    
    class B2 {
    public:
      virtual void f() {
        printf("B2::f
    ");
      }
      virtual void g() {
        printf("B2::g
    ");
      }
      virtual void h() {
        printf("B2::h
    ");
      }
    
      int i,j;
    };
    
    class D : public B, public B2 {
    public:
      virtual void f() {
        printf("D::f
    ");
      }
      virtual void dg() {
        printf("D::dg
    ");
      }
      virtual void dh() {
        printf("D::dh
    ");
      }
    
      int x,y;
    };
    
    using Func = void(*)(void);
    
    int main() {
      B b;
      printf("sizeof b: %ld
    ", sizeof(b));
      printf("addr B:
    %p
    %p
    %p
    ", &b, &b.m, &b.n);
      printf("vtable B: %p
    ", *(long***)&b);
      printf("------------
    ");
    
      B2 b2;
      printf("sizeof b2: %ld
    ", sizeof(b2));
      printf("addr B2:
    %p
    %p
    %p
    ", &b2, &b2.i, &b2.j);
      printf("vtable B2: %p
    ", *(long***)&b2);
      printf("------------
    ");
    
      D d;
      printf("sizeof d:%ld
    ", sizeof(d));
      printf("addr D:
    %p
    %p
    %p
    %p
    %p
    %p
    %p
    ", &d, &d.m, &d.n, &d.i, &d.j, &d.x, &d.y);
      printf("vtable D: %p
    ", *(long***)&d);
      printf("------------
    ");
    
      // first vtable pointer
      long** vt1 = (long**)*(long***)&d;
      Func p = nullptr;
      for (int i = 0; i < 5; ++i)
      {
        p = (Func)*(vt1+i);
        p();
      }
    
      // second vtable pointer. member 'm' and 'n' type is int, pointer +2
      long** vt2 = (long**)*((long***)&d + 2);
      for (int i = 0; i < 3; ++i)
      {
        p = (Func)*(vt2+i);
        p();
      }
    
      return 0;
    }
    

    结果:

    sizeof b: 16
    addr B:
    0x7ffe091424e0
    0x7ffe091424e8
    0x7ffe091424ec
    vtable B: 0x5598cb9edd38
    ------------
    sizeof b2: 16
    addr B2:
    0x7ffe091424f0
    0x7ffe091424f8
    0x7ffe091424fc
    vtable B2: 0x5598cb9edd10
    ------------
    sizeof d:40
    addr D:
    0x7ffe09142500
    0x7ffe09142508
    0x7ffe0914250c
    0x7ffe09142518
    0x7ffe0914251c
    0x7ffe09142520
    0x7ffe09142524
    vtable D: 0x5598cb9edcb0
    ------------
    D::f
    B::g
    B::h
    D::dg
    D::dh
    D::f
    B2::g
    B2::h
    

    此时的内存布局为:

    基类虚函数表中的f函数都被替换成了子类的函数指针

  • 相关阅读:
    非空约束
    leetcode208
    leetcode207
    leetcode395
    leetcode116
    leetcode105
    leetcode131
    leetcode73
    leetcode200
    leetcode17
  • 原文地址:https://www.cnblogs.com/zuofaqi/p/12152468.html
Copyright © 2011-2022 走看看