探索C++对象模型

本文分析了在不同场景下的C++对象模型

1. 前言

1.1 测试环境

  • Linux ubuntu18arm64 4.15.0-76-generic #86-Ubuntu SMP Fri Jan 17 17:25:58 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux
  • gcc version 7.4.0 (Ubuntu/Linaro 7.4.0-1ubuntu1~18.04.1)
  • glibc 2.27
  • C++11

1.2 测试代码

class Base {
 public:
  Base(): m_base(1) {}

  virtual ~Base() {
    m_base = 0;
  }

  virtual void foo() {
    m_base++;
  }

  virtual void bar() {
    m_base--;
  }


 private:
  int m_base;
};

class Base1 : public Base {
 public:
  Base1(): m_base1(21) {}

  virtual ~Base1() {
    m_base1 = 0;
  }

  virtual void foo() {
    m_base1++;
  }

 private:
  int m_base1;
};

class Base2 : virtual public Base {
 public:
  Base2(): m_base2(22) {}

  virtual ~Base2() {
    m_base2 = 0;
  }

  virtual void foo() {
   m_base2++;
  }

 private:
  int m_base2;
};


class Base3 : virtual public Base {
 public:
  Base3(): m_base3(23) {}

  virtual ~Base3() {
    m_base3 = 0;
  }

  virtual void foo() {
   m_base3++;
  }

 private:
  int m_base3;
};

class Derived : public Base2, public Base3 {
 public:
  Derived(): m_derived(3) {}

  virtual ~Derived() {
    m_derived = 0;
  }

  virtual void foo() {
    m_derived++;
  }

 private:
  int m_derived;
};

int main(int argc, char *argv[]) {

  Base b, *pb = nullptr;
  Base1 b1;
  Base2 b2;
  Base3 b3;
  Derived d;

  // Base and foo
  b.foo();
  pb = &b;
  pb->foo();

  // single inheritance
  b1.foo();
  b1.bar();
  pb = &b1;
  pb->foo();
  pb->bar();

  // virtual single inheritance
  pb = &b2;
  pb->foo();

  pb = &b3;
  pb->foo();

  // virtual multiple inheritance
  b2.foo();
  Base2 *pb2 = &d;
  pb2->foo();

  b3.foo();
  Base3 *pb3 = &d;
  pb3->foo();

  pb = &d;
  pb->foo();

  d.foo();
  d.bar();

  return 0;
}

各类的继承关系图如下

base.jpeg

2. 调试分析

2.1 普通类

普通类可以有虚函数, 也可以没有. 这里以带虚函数的Base b对象为例

2.1.1 内存分析

查看对象信息

(gdb) p /x &b
$2 = 0xfffffffff218

(gdb) p sizeof(b)
$3 = 16

(gdb) x/2xg 0xfffffffff218
0xfffffffff218: 0x0000aaaaaaabcc58  0x0000aaaa00000001

查看_vptr.Base

(gdb) x/8xg 0x0000aaaaaaabcc58
0xaaaaaaabcc58 <_ZTV4Base+16>:  0x0000aaaaaaaab55c  0x0000aaaaaaaab588
0xaaaaaaabcc68 <_ZTV4Base+32>:  0x0000aaaaaaaab5ac  0x0000aaaaaaaab5d4
0xaaaaaaabcc78 <_ZTI7Derived>:  0x0000fffff7fc4338  0x0000aaaaaaaabcd0
0xaaaaaaabcc88 <_ZTI7Derived+16>:   0x0000000200000002  0x0000aaaaaaabccd8

(gdb) disas 0x0000aaaaaaaab55c
Dump of assembler code for function Base::~Base():
   0x0000aaaaaaaab55c <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaab560 <+4>: str x0, [sp, #8]
   0x0000aaaaaaaab564 <+8>: adrp    x0, 0xaaaaaaabc000
   0x0000aaaaaaaab568 <+12>:    add x1, x0, #0xc58
   0x0000aaaaaaaab56c <+16>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab570 <+20>:    str x1, [x0]
   0x0000aaaaaaaab574 <+24>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab578 <+28>:    str wzr, [x0, #8]
   0x0000aaaaaaaab57c <+32>:    nop
   0x0000aaaaaaaab580 <+36>:    add sp, sp, #0x10
   0x0000aaaaaaaab584 <+40>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab588
Dump of assembler code for function Base::~Base():
   0x0000aaaaaaaab588 <+0>: stp x29, x30, [sp, #-32]!
   0x0000aaaaaaaab58c <+4>: mov x29, sp
   0x0000aaaaaaaab590 <+8>: str x0, [x29, #24]
   0x0000aaaaaaaab594 <+12>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab598 <+16>:    bl  0xaaaaaaaab55c <Base::~Base()>
   0x0000aaaaaaaab59c <+20>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab5a0 <+24>:    bl  0xaaaaaaaab110 <_ZdlPv@plt>
   0x0000aaaaaaaab5a4 <+28>:    ldp x29, x30, [sp], #32
   0x0000aaaaaaaab5a8 <+32>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5ac
Dump of assembler code for function Base::foo():
   0x0000aaaaaaaab5ac <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaab5b0 <+4>: str x0, [sp, #8]
   0x0000aaaaaaaab5b4 <+8>: ldr x0, [sp, #8]
   0x0000aaaaaaaab5b8 <+12>:    ldr w0, [x0, #8]
   0x0000aaaaaaaab5bc <+16>:    add w1, w0, #0x1
   0x0000aaaaaaaab5c0 <+20>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab5c4 <+24>:    str w1, [x0, #8]
   0x0000aaaaaaaab5c8 <+28>:    nop
   0x0000aaaaaaaab5cc <+32>:    add sp, sp, #0x10
   0x0000aaaaaaaab5d0 <+36>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5d4
Dump of assembler code for function Base::bar():
   0x0000aaaaaaaab5d4 <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaab5d8 <+4>: str x0, [sp, #8]
   0x0000aaaaaaaab5dc <+8>: ldr x0, [sp, #8]
   0x0000aaaaaaaab5e0 <+12>:    ldr w0, [x0, #8]
   0x0000aaaaaaaab5e4 <+16>:    sub w1, w0, #0x1
   0x0000aaaaaaaab5e8 <+20>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab5ec <+24>:    str w1, [x0, #8]
   0x0000aaaaaaaab5f0 <+28>:    nop
   0x0000aaaaaaaab5f4 <+32>:    add sp, sp, #0x10
   0x0000aaaaaaaab5f8 <+36>:    ret
End of assembler dump.

2.1.2 对象模型

根据前面的分析, 对Base类(包含虚函数)的实例b, 其对象模型如下

+-----------+            
|_vptr.Base |   ---->    +-------------+ 
+-----------+            |Base::~Base()|
|  m_base   |            +-------------+ 
+-----------+            |Base::~Base()|
                         +-------------+
                         | Base::foo() |
                         +-------------+
                         | Base::bar() |
                         +-------------+                         

这里vtable中有两个版本的Base::~Base()

  • 第1个Base::~Base()完成Base类的析构函数功能. 以前提到的栈对象/局部静态对象/全局对象析构时会调用该版本
  • 第2个Base::~Base()先调用第1个Base::~Base(),然后释放对象占用的内存. 堆对象析构时会调用该版本.

2.1.3 代码分析

通过实例直接调用虚函数

  b.foo();

反汇编如下

   0x0000aaaaaaaab2d8 <+84>:    add x0, x29, #0x48
   0x0000aaaaaaaab2dc <+88>:    bl  0xaaaaaaaab5ac <Base::foo()>

通过指针调用虚函数

  pb = &b;
  pb->foo();

反汇编如下

   0x0000aaaaaaaab2e0 <+92>:    add x0, x29, #0x48
   0x0000aaaaaaaab2e4 <+96>:    str x0, [x29, #48]
   0x0000aaaaaaaab2e8 <+100>:   ldr x0, [x29, #48]
   
   0x0000aaaaaaaab2ec <+104>:   ldr x0, [x0]
   
   0x0000aaaaaaaab2f0 <+108>:   add x0, x0, #0x10
   0x0000aaaaaaaab2f4 <+112>:   ldr x1, [x0]
   
   0x0000aaaaaaaab2f8 <+116>:   ldr x0, [x29, #48]
   0x0000aaaaaaaab2fc <+120>:   blr x1

流程如下

  • 确定当前对象地址(x0 = x29 + 0x48)
  • 找到vptr(gcc下默认就是[x0])
  • 在vtable中找到要调用的虚函数(vtable[2])
  • 跳转到虚函数执行

2.2 单一继承

这里以Base1 b1对象为例

2.2.1 内存分析

查看对象信息

(gdb) p /x &b1
$4 = 0xfffffffff228

(gdb) p sizeof(b1)
$5 = 16

(gdb) x/2xg 0xfffffffff228
0xfffffffff228: 0x0000aaaaaaabcc28  0x0000001500000001

查看_vptr.Base

(gdb) x/8xg 0x0000aaaaaaabcc28
0xaaaaaaabcc28 <_ZTV5Base1+16>: 0x0000aaaaaaaab638  0x0000aaaaaaaab670
0xaaaaaaabcc38 <_ZTV5Base1+32>: 0x0000aaaaaaaab694  0x0000aaaaaaaab5d4
0xaaaaaaabcc48 <_ZTV4Base>: 0x0000000000000000  0x0000aaaaaaabcd18
0xaaaaaaabcc58 <_ZTV4Base+16>:  0x0000aaaaaaaab55c  0x0000aaaaaaaab588

(gdb) disas 0x0000aaaaaaaab638
Dump of assembler code for function Base1::~Base1():
   0x0000aaaaaaaab638 <+0>: stp x29, x30, [sp, #-32]!
   0x0000aaaaaaaab63c <+4>: mov x29, sp
   0x0000aaaaaaaab640 <+8>: str x0, [x29, #24]
   0x0000aaaaaaaab644 <+12>:    adrp    x0, 0xaaaaaaabc000
   0x0000aaaaaaaab648 <+16>:    add x1, x0, #0xc28
   0x0000aaaaaaaab64c <+20>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab650 <+24>:    str x1, [x0]
   0x0000aaaaaaaab654 <+28>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab658 <+32>:    str wzr, [x0, #12]
   0x0000aaaaaaaab65c <+36>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab660 <+40>:    bl  0xaaaaaaaab55c <Base::~Base()>
   0x0000aaaaaaaab664 <+44>:    nop
   0x0000aaaaaaaab668 <+48>:    ldp x29, x30, [sp], #32
   0x0000aaaaaaaab66c <+52>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab670
Dump of assembler code for function Base1::~Base1():
   0x0000aaaaaaaab670 <+0>: stp x29, x30, [sp, #-32]!
   0x0000aaaaaaaab674 <+4>: mov x29, sp
   0x0000aaaaaaaab678 <+8>: str x0, [x29, #24]
   0x0000aaaaaaaab67c <+12>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab680 <+16>:    bl  0xaaaaaaaab638 <Base1::~Base1()>
   0x0000aaaaaaaab684 <+20>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab688 <+24>:    bl  0xaaaaaaaab110 <_ZdlPv@plt>
   0x0000aaaaaaaab68c <+28>:    ldp x29, x30, [sp], #32
   0x0000aaaaaaaab690 <+32>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab694
Dump of assembler code for function Base1::foo():
   0x0000aaaaaaaab694 <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaab698 <+4>: str x0, [sp, #8]
   0x0000aaaaaaaab69c <+8>: ldr x0, [sp, #8]
   0x0000aaaaaaaab6a0 <+12>:    ldr w0, [x0, #12]
   0x0000aaaaaaaab6a4 <+16>:    add w1, w0, #0x1
   0x0000aaaaaaaab6a8 <+20>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab6ac <+24>:    str w1, [x0, #12]
   0x0000aaaaaaaab6b0 <+28>:    nop
   0x0000aaaaaaaab6b4 <+32>:    add sp, sp, #0x10
   0x0000aaaaaaaab6b8 <+36>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5d4
Dump of assembler code for function Base::bar():
   0x0000aaaaaaaab5d4 <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaab5d8 <+4>: str x0, [sp, #8]
   0x0000aaaaaaaab5dc <+8>: ldr x0, [sp, #8]
   0x0000aaaaaaaab5e0 <+12>:    ldr w0, [x0, #8]
   0x0000aaaaaaaab5e4 <+16>:    sub w1, w0, #0x1
   0x0000aaaaaaaab5e8 <+20>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab5ec <+24>:    str w1, [x0, #8]
   0x0000aaaaaaaab5f0 <+28>:    nop
   0x0000aaaaaaaab5f4 <+32>:    add sp, sp, #0x10
   0x0000aaaaaaaab5f8 <+36>:    ret
End of assembler dump.

2.2.2 对象模型

根据前面的分析,对Base1类(包含虚函数 + 单一继承)的实例b1, 其对象模型如下

+-----------+            
|_vptr.Base |   ---->    +---------------+
+-----------+            |Base1::~Base1()|
|  m_base   |            +---------------+ 
+-----------+            |Base1::~Base1()|
|  m_base1  |            +---------------+
+-----------+            |  Base1::foo() |
                         +---------------+
                         |  Base::bar()  |
                         +---------------+                         
  • 单一继承情况下, 虽然_vptr.Base包含基类名, 但实际是Base1类的vptr
  • Base1 override基类Base的foo(), 但没有override基类Base的bar(), 所以Base1虚表中的foo()是Base1::foo(), bar()是Base::bar().

2.2.3 代码分析

通过实例直接调用虚函数

  b1.foo();

反汇编如下

   0x0000aaaaaaaab300 <+124>:   add x0, x29, #0x58
   0x0000aaaaaaaab304 <+128>:   bl  0xaaaaaaaab694 <Base1::foo()>

通过基类指针调用虚函数

  pb = &b1;
  pb->foo();

反汇编如下

   0x0000aaaaaaaab310 <+140>:   add x0, x29, #0x58
   0x0000aaaaaaaab314 <+144>:   str x0, [x29, #48]
   0x0000aaaaaaaab318 <+148>:   ldr x0, [x29, #48]
   
   0x0000aaaaaaaab31c <+152>:   ldr x0, [x0]
   
   0x0000aaaaaaaab320 <+156>:   add x0, x0, #0x10
   0x0000aaaaaaaab324 <+160>:   ldr x1, [x0]
   
   0x0000aaaaaaaab328 <+164>:   ldr x0, [x29, #48]
   0x0000aaaaaaaab32c <+168>:   blr x1

2.3 虚继承1

先看虚继承简单的情形, 这里以Base2 b2对象为例

2.3.1 内存分析

查看对象信息

(gdb) p /x &b2
$6 = 0xfffffffff238

(gdb) p sizeof(b2)
$7 = 32

(gdb) x/4xg 0xfffffffff238
0xfffffffff238: 0x0000aaaaaaabcba8  0x0000ffff00000016
0xfffffffff248: 0x0000aaaaaaabcbe8  0x0000ffff00000001

这里有两个vptr

  • _vptr.Base2 = 0x0000aaaaaaabcba8
  • _vptr.Base = 0x0000aaaaaaabcbe8

查看_vptr.Base2

(gdb) x/8xg 0x0000aaaaaaabcba8
0xaaaaaaabcba8 <_ZTV5Base2+24>: 0x0000aaaaaaaab7c4  0x0000aaaaaaaab824
0xaaaaaaabcbb8 <_ZTV5Base2+40>: 0x0000aaaaaaaab858  0x0000000000000000
0xaaaaaaabcbc8 <_ZTV5Base2+56>: 0xfffffffffffffff0  0xfffffffffffffff0
0xaaaaaaabcbd8 <_ZTV5Base2+72>: 0xfffffffffffffff0  0x0000aaaaaaabccd8

(gdb) disas 0x0000aaaaaaaab7c4
Dump of assembler code for function Base2::~Base2():
   0x0000aaaaaaaab7c4 <+0>: stp x29, x30, [sp, #-32]!
   0x0000aaaaaaaab7c8 <+4>: mov x29, sp
   0x0000aaaaaaaab7cc <+8>: str x0, [x29, #24]
   0x0000aaaaaaaab7d0 <+12>:    adrp    x0, 0xaaaaaaabc000
   0x0000aaaaaaaab7d4 <+16>:    add x1, x0, #0xba8
   0x0000aaaaaaaab7d8 <+20>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab7dc <+24>:    str x1, [x0]
   0x0000aaaaaaaab7e0 <+28>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab7e4 <+32>:    add x0, x0, #0x10
   0x0000aaaaaaaab7e8 <+36>:    adrp    x1, 0xaaaaaaabc000
   0x0000aaaaaaaab7ec <+40>:    add x1, x1, #0xbe8
   0x0000aaaaaaaab7f0 <+44>:    str x1, [x0]
   0x0000aaaaaaaab7f4 <+48>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab7f8 <+52>:    str wzr, [x0, #8]
   0x0000aaaaaaaab7fc <+56>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab800 <+60>:    add x0, x0, #0x10
   0x0000aaaaaaaab804 <+64>:    bl  0xaaaaaaaab55c <Base::~Base()>
   0x0000aaaaaaaab808 <+68>:    nop
   0x0000aaaaaaaab80c <+72>:    ldp x29, x30, [sp], #32
   0x0000aaaaaaaab810 <+76>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab824
Dump of assembler code for function Base2::~Base2():
   0x0000aaaaaaaab824 <+0>: stp x29, x30, [sp, #-32]!
   0x0000aaaaaaaab828 <+4>: mov x29, sp
   0x0000aaaaaaaab82c <+8>: str x0, [x29, #24]
   0x0000aaaaaaaab830 <+12>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab834 <+16>:    bl  0xaaaaaaaab7c4 <Base2::~Base2()>
   0x0000aaaaaaaab838 <+20>:    ldr x0, [x29, #24]
   0x0000aaaaaaaab83c <+24>:    bl  0xaaaaaaaab110 <_ZdlPv@plt>
   0x0000aaaaaaaab840 <+28>:    ldp x29, x30, [sp], #32
   0x0000aaaaaaaab844 <+32>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab858
Dump of assembler code for function Base2::foo():
   0x0000aaaaaaaab858 <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaab85c <+4>: str x0, [sp, #8]
   0x0000aaaaaaaab860 <+8>: ldr x0, [sp, #8]
   0x0000aaaaaaaab864 <+12>:    ldr w0, [x0, #8]
   0x0000aaaaaaaab868 <+16>:    add w1, w0, #0x1
   0x0000aaaaaaaab86c <+20>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab870 <+24>:    str w1, [x0, #8]
   0x0000aaaaaaaab874 <+28>:    nop
   0x0000aaaaaaaab878 <+32>:    add sp, sp, #0x10
   0x0000aaaaaaaab87c <+36>:    ret
End of assembler dump.

查看_vptr.Base

(gdb) x/8xg 0x0000aaaaaaabcbe8
0xaaaaaaabcbe8 <_ZTV5Base2+88>: 0x0000aaaaaaaab814  0x0000aaaaaaaab848
0xaaaaaaabcbf8 <_ZTV5Base2+104>:    0x0000aaaaaaaab880  0x0000aaaaaaaab5d4
0xaaaaaaabcc08 <_ZTT5Base2>:    0x0000aaaaaaabcba8  0x0000aaaaaaabcbe8
0xaaaaaaabcc18 <_ZTV5Base1>:    0x0000000000000000  0x0000aaaaaaabcd00

(gdb) disas 0x0000aaaaaaaab814
Dump of assembler code for function _ZTv0_n24_N5Base2D1Ev:
   0x0000aaaaaaaab814 <+0>: ldr x16, [x0]
   0x0000aaaaaaaab818 <+4>: ldur    x17, [x16, #-24]
   0x0000aaaaaaaab81c <+8>: add x0, x0, x17
   0x0000aaaaaaaab820 <+12>:    b   0xaaaaaaaab7c4 <Base2::~Base2()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab848
Dump of assembler code for function _ZTv0_n24_N5Base2D0Ev:
   0x0000aaaaaaaab848 <+0>: ldr x16, [x0]
   0x0000aaaaaaaab84c <+4>: ldur    x17, [x16, #-24]
   0x0000aaaaaaaab850 <+8>: add x0, x0, x17
   0x0000aaaaaaaab854 <+12>:    b   0xaaaaaaaab824 <Base2::~Base2()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab880
Dump of assembler code for function _ZTv0_n32_N5Base23fooEv:
   0x0000aaaaaaaab880 <+0>: ldr x16, [x0]
   0x0000aaaaaaaab884 <+4>: ldur    x17, [x16, #-32]
   0x0000aaaaaaaab888 <+8>: add x0, x0, x17
   0x0000aaaaaaaab88c <+12>:    b   0xaaaaaaaab858 <Base2::foo()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5d4
Dump of assembler code for function Base::bar():
   0x0000aaaaaaaab5d4 <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaab5d8 <+4>: str x0, [sp, #8]
   0x0000aaaaaaaab5dc <+8>: ldr x0, [sp, #8]
   0x0000aaaaaaaab5e0 <+12>:    ldr w0, [x0, #8]
   0x0000aaaaaaaab5e4 <+16>:    sub w1, w0, #0x1
   0x0000aaaaaaaab5e8 <+20>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab5ec <+24>:    str w1, [x0, #8]
   0x0000aaaaaaaab5f0 <+28>:    nop
   0x0000aaaaaaaab5f4 <+32>:    add sp, sp, #0x10
   0x0000aaaaaaaab5f8 <+36>:    ret
End of assembler dump.

几点说明:

  • 这里的_vptr.Base(0x0000aaaaaaabcbe8)并不是Base类真正的vptr(0x0000aaaaaaabcc58)
  • 前面的三个函数是编译器生成的thunk函数,用于修正对象地址(this指针由Base子对象地址切换到Base2对象地址),跳转到Base2 override的虚函数。
  • 最后一个虚函数是Base::bar(), Base2没有override, 这里直接存储其地址, this指针为Base子对象地址

2.3.2 对象模型

根据前面的分析,对Base2类(虚继承)的实例b2, 其对象模型如下

+-----------+            
|_vptr.Base2|   ---->    +---------------+
+-----------+            |Base2::~Base2()| <------------------
|  m_base2  |            +---------------+                   |
+-----------+            |Base2::~Base2()| <-----------------|--
|_vptr.Base |  ----|     +---------------+                   | |
+-----------+      |     |  Base2::foo() | <-----------------|-|--
|   m_base  |      |     +---------------+                   | | |
+-----------+      |                                         | | |
                   | --> +--------------------------------+  | | |
                         |virtual thunk to Base2::~Base2()|--- | |
                         +--------------------------------+    | |
                         |virtual thunk to Base2::~Base2()|----- |
                         +--------------------------------+      |
                         | virtual thunk to Base2::foo()  |-------
                         +--------------------------------+
                         |           Base::bar()          |
                         +--------------------------------+

2.3.3 代码分析

通过实例直接调用虚函数

b2.foo();

反汇编如下

   0x0000aaaaaaaab390 <+268>:   add x0, x29, #0x68
   0x0000aaaaaaaab394 <+272>:   bl  0xaaaaaaaab858 <Base2::foo()>

通过基类指针调用虚函数

  pb = &b2;
  pb->foo();

对应汇编如下:

   0x0000aaaaaaaab348 <+196>:   add x0, x29, #0x68
   
   0x0000aaaaaaaab34c <+200>:   add x0, x0, #0x10
   0x0000aaaaaaaab350 <+204>:   str x0, [x29, #48]
   0x0000aaaaaaaab354 <+208>:   ldr x0, [x29, #48]
   
   0x0000aaaaaaaab358 <+212>:   ldr x0, [x0]
   
   0x0000aaaaaaaab35c <+216>:   add x0, x0, #0x10
   0x0000aaaaaaaab360 <+220>:   ldr x1, [x0]
   
   0x0000aaaaaaaab364 <+224>:   ldr x0, [x29, #48]
   0x0000aaaaaaaab368 <+228>:   blr x1

通过上面代码可以看到, 虚继承和单一继承在通过基类指针访问派生类对象过程中,
流程不同.

  • 派生类对象地址加上特定的偏移得到基类子对象地址(编译器在处理pb = &b2后, 此时的pb已经不是b2的地址了, 而是&b2 + 0x10, 指向Base子对象)
  • 根据基类子对象地址, 找到_vptr.Base
  • 根据要调用的虚函数, 在vtable找到对应项: thunk函数基类版本
  • thunk函数是一段汇编代码, 通过修正对象地址(基类子对象地址切换到派生类对象地址), 最终跳转到派生类版本执行

2.4 虚继承2

最后看看虚继承最复杂的情形, 这里以Derived d对象为例

2.4.1 内存分析

查看对象信息

(gdb) p /x &d
$10 = 0xfffffffff278

(gdb) p sizeof(d)
$11 = 48

(gdb) x/6xg 0xfffffffff278
0xfffffffff278: 0x0000aaaaaaabc950  0x0000aaaa00000016
0xfffffffff288: 0x0000aaaaaaabc980  0x0000000500000017
0xfffffffff298: 0x0000aaaaaaabc9c0  0x0000aaaa00000001

查看_vptr.Base2

(gdb) x/8xg 0x0000aaaaaaabc950
0xaaaaaaabc950 <_ZTV7Derived+24>:   0x0000aaaaaaaabafc  0x0000aaaaaaaabba8
0xaaaaaaabc960 <_ZTV7Derived+40>:   0x0000aaaaaaaabbe4  0x0000000000000010
0xaaaaaaabc970 <_ZTV7Derived+56>:   0xfffffffffffffff0  0x0000aaaaaaabcc78
0xaaaaaaabc980 <_ZTV7Derived+72>:   0x0000aaaaaaaabba0  0x0000aaaaaaaabbdc

(gdb) disas 0x0000aaaaaaaabafc
Dump of assembler code for function Derived::~Derived():
   0x0000aaaaaaaabafc <+0>: stp x29, x30, [sp, #-32]!
   0x0000aaaaaaaabb00 <+4>: mov x29, sp
   0x0000aaaaaaaabb04 <+8>: str x0, [x29, #24]
   0x0000aaaaaaaabb08 <+12>:    adrp    x0, 0xaaaaaaabc000
   0x0000aaaaaaaabb0c <+16>:    add x1, x0, #0x950
   0x0000aaaaaaaabb10 <+20>:    ldr x0, [x29, #24]
   0x0000aaaaaaaabb14 <+24>:    str x1, [x0]
   0x0000aaaaaaaabb18 <+28>:    ldr x0, [x29, #24]
   0x0000aaaaaaaabb1c <+32>:    add x0, x0, #0x20
   0x0000aaaaaaaabb20 <+36>:    adrp    x1, 0xaaaaaaabc000
   0x0000aaaaaaaabb24 <+40>:    add x1, x1, #0x9c0
   0x0000aaaaaaaabb28 <+44>:    str x1, [x0]
   0x0000aaaaaaaabb2c <+48>:    adrp    x0, 0xaaaaaaabc000
   0x0000aaaaaaaabb30 <+52>:    add x1, x0, #0x980
   0x0000aaaaaaaabb34 <+56>:    ldr x0, [x29, #24]
   0x0000aaaaaaaabb38 <+60>:    str x1, [x0, #16]
   0x0000aaaaaaaabb3c <+64>:    ldr x0, [x29, #24]
   0x0000aaaaaaaabb40 <+68>:    str wzr, [x0, #28]
   0x0000aaaaaaaabb44 <+72>:    ldr x0, [x29, #24]
   0x0000aaaaaaaabb48 <+76>:    add x2, x0, #0x10
   0x0000aaaaaaaabb4c <+80>:    adrp    x0, 0xaaaaaaabc000
   0x0000aaaaaaaabb50 <+84>:    add x0, x0, #0x9f8
   0x0000aaaaaaaabb54 <+88>:    mov x1, x0
   0x0000aaaaaaaabb58 <+92>:    mov x0, x2
   0x0000aaaaaaaabb5c <+96>:    bl  0xaaaaaaaab940 <Base3::~Base3()>
   0x0000aaaaaaaabb60 <+100>:   ldr x2, [x29, #24]
   0x0000aaaaaaaabb64 <+104>:   adrp    x0, 0xaaaaaaabc000
   0x0000aaaaaaaabb68 <+108>:   add x0, x0, #0x9e8
   0x0000aaaaaaaabb6c <+112>:   mov x1, x0
   0x0000aaaaaaaabb70 <+116>:   mov x0, x2
   0x0000aaaaaaaabb74 <+120>:   bl  0xaaaaaaaab76c <Base2::~Base2()>
   0x0000aaaaaaaabb78 <+124>:   ldr x0, [x29, #24]
   0x0000aaaaaaaabb7c <+128>:   add x0, x0, #0x20
   0x0000aaaaaaaabb80 <+132>:   bl  0xaaaaaaaab55c <Base::~Base()>
   0x0000aaaaaaaabb84 <+136>:   nop
   0x0000aaaaaaaabb88 <+140>:   ldp x29, x30, [sp], #32
   0x0000aaaaaaaabb8c <+144>:   ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabba8
Dump of assembler code for function Derived::~Derived():
   0x0000aaaaaaaabba8 <+0>: stp x29, x30, [sp, #-32]!
   0x0000aaaaaaaabbac <+4>: mov x29, sp
   0x0000aaaaaaaabbb0 <+8>: str x0, [x29, #24]
   0x0000aaaaaaaabbb4 <+12>:    ldr x0, [x29, #24]
   0x0000aaaaaaaabbb8 <+16>:    bl  0xaaaaaaaabafc <Derived::~Derived()>
   0x0000aaaaaaaabbbc <+20>:    ldr x0, [x29, #24]
   0x0000aaaaaaaabbc0 <+24>:    bl  0xaaaaaaaab110 <_ZdlPv@plt>
   0x0000aaaaaaaabbc4 <+28>:    ldp x29, x30, [sp], #32
   0x0000aaaaaaaabbc8 <+32>:    ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabbe4
Dump of assembler code for function Derived::foo():
   0x0000aaaaaaaabbe4 <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaabbe8 <+4>: str x0, [sp, #8]
=> 0x0000aaaaaaaabbec <+8>: ldr x0, [sp, #8]
   0x0000aaaaaaaabbf0 <+12>:    ldr w0, [x0, #28]
   0x0000aaaaaaaabbf4 <+16>:    add w1, w0, #0x1
   0x0000aaaaaaaabbf8 <+20>:    ldr x0, [sp, #8]
   0x0000aaaaaaaabbfc <+24>:    str w1, [x0, #28]
   0x0000aaaaaaaabc00 <+28>:    nop
   0x0000aaaaaaaabc04 <+32>:    add sp, sp, #0x10
   0x0000aaaaaaaabc08 <+36>:    ret
End of assembler dump.

Base2类是Derived派生类列表的第一个直接基类, 这里的_vptr.Base2实际是Derived类的vptr

查看_vptr.Base3

(gdb) x/8xg 0x0000aaaaaaabc980
0xaaaaaaabc980 <_ZTV7Derived+72>:   0x0000aaaaaaaabba0  0x0000aaaaaaaabbdc
0xaaaaaaabc990 <_ZTV7Derived+88>:   0x0000aaaaaaaabc1c  0x0000000000000000
0xaaaaaaabc9a0 <_ZTV7Derived+104>:  0xffffffffffffffe0  0xffffffffffffffe0
0xaaaaaaabc9b0 <_ZTV7Derived+120>:  0xffffffffffffffe0  0x0000aaaaaaabcc78

(gdb) disas 0x0000aaaaaaaabba0
Dump of assembler code for function _ZThn16_N7DerivedD1Ev:
   0x0000aaaaaaaabba0 <+0>: sub x0, x0, #0x10
   0x0000aaaaaaaabba4 <+4>: b   0xaaaaaaaabafc <Derived::~Derived()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabbdc
Dump of assembler code for function _ZThn16_N7DerivedD0Ev:
   0x0000aaaaaaaabbdc <+0>: sub x0, x0, #0x10
   0x0000aaaaaaaabbe0 <+4>: b   0xaaaaaaaabba8 <Derived::~Derived()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabc1c
Dump of assembler code for function _ZThn16_N7Derived3fooEv:
   0x0000aaaaaaaabc1c <+0>: sub x0, x0, #0x10
   0x0000aaaaaaaabc20 <+4>: b   0xaaaaaaaabbe4 <Derived::foo()>
End of assembler dump.

Base3类是Derived派生类列表的第二个直接基类, 这里的_vptr.Base3并不是Base3类的vptr.

_vptr.Base

(gdb) x/8xg 0x0000aaaaaaabc9c0
0xaaaaaaabc9c0 <_ZTV7Derived+136>:  0x0000aaaaaaaabb90  0x0000aaaaaaaabbcc
0xaaaaaaabc9d0 <_ZTV7Derived+152>:  0x0000aaaaaaaabc0c  0x0000aaaaaaaab5d4
0xaaaaaaabc9e0 <_ZTT7Derived>:  0x0000aaaaaaabc950  0x0000aaaaaaabca30
0xaaaaaaabc9f0 <_ZTT7Derived+16>:   0x0000aaaaaaabca70  0x0000aaaaaaabcaa8

(gdb) disas 0x0000aaaaaaaabb90
Dump of assembler code for function _ZTv0_n24_N7DerivedD1Ev:
   0x0000aaaaaaaabb90 <+0>: ldr x16, [x0]
   0x0000aaaaaaaabb94 <+4>: ldur    x17, [x16, #-24]
   0x0000aaaaaaaabb98 <+8>: add x0, x0, x17
   0x0000aaaaaaaabb9c <+12>:    b   0xaaaaaaaabafc <Derived::~Derived()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabbcc
Dump of assembler code for function _ZTv0_n24_N7DerivedD0Ev:
   0x0000aaaaaaaabbcc <+0>: ldr x16, [x0]
   0x0000aaaaaaaabbd0 <+4>: ldur    x17, [x16, #-24]
   0x0000aaaaaaaabbd4 <+8>: add x0, x0, x17
   0x0000aaaaaaaabbd8 <+12>:    b   0xaaaaaaaabba8 <Derived::~Derived()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabc0c
Dump of assembler code for function _ZTv0_n32_N7Derived3fooEv:
   0x0000aaaaaaaabc0c <+0>: ldr x16, [x0]
   0x0000aaaaaaaabc10 <+4>: ldur    x17, [x16, #-32]
   0x0000aaaaaaaabc14 <+8>: add x0, x0, x17
   0x0000aaaaaaaabc18 <+12>:    b   0xaaaaaaaabbe4 <Derived::foo()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5d4
Dump of assembler code for function Base::bar():
   0x0000aaaaaaaab5d4 <+0>: sub sp, sp, #0x10
   0x0000aaaaaaaab5d8 <+4>: str x0, [sp, #8]
   0x0000aaaaaaaab5dc <+8>: ldr x0, [sp, #8]
   0x0000aaaaaaaab5e0 <+12>:    ldr w0, [x0, #8]
   0x0000aaaaaaaab5e4 <+16>:    sub w1, w0, #0x1
   0x0000aaaaaaaab5e8 <+20>:    ldr x0, [sp, #8]
   0x0000aaaaaaaab5ec <+24>:    str w1, [x0, #8]
   0x0000aaaaaaaab5f0 <+28>:    nop
   0x0000aaaaaaaab5f4 <+32>:    add sp, sp, #0x10
   0x0000aaaaaaaab5f8 <+36>:    ret
End of assembler dump.

2.4.2 对象模型

根据前面的分析,对Derived类(虚函数 + 虚继承)的实例d, 其对象模型如下

+-------------+            
| _vptr.Base2 |   ---->    +-------------------+
+-------------+            |Derived::~Derived()| <---------------------------
|  m_base2    |            +-------------------+                      |     |
+-------------+            |Derived::~Derived()| <--------------------|-----|--
| _vptr.Base3 |   ----     +-------------------+                      | |   | | 
+-------------+      |     |   Derived::foo()  | <--------------------|-|---|-|-- 
|  m_base3    |      |     +-------------------+                      | | | | | |
+-------------+      |                                                | | | | | |
|  m_derived  |      ----> +----------------------------------------+ | | | | | |
+-------------+            |non-virtual thunk to Derived::~Derived()|-- | | | | |
| _vptr.Base  |   ----     +----------------------------------------+   | | | | |
+-------------+      |     |non-virtual thunk to Derived::~Derived()|---- | | | |
|   m_base    |      |     +----------------------------------------+     | | | |
+-------------+      |     |  non-virtual thunk to Derived::foo()   |------ | | |
                     |     +----------------------------------------+       | | |
                     |                                                      | | |
                     ----> +------------------------------------+           | | |
                           |virtual thunk to Derived::~Derived()|------------ | |
                           +------------------------------------+             | |
                           |virtual thunk to Derived::~Derived()|-------------- |
                           +------------------------------------+               |
                           |   virtual thunk to Derived::foo()  |---------------- 
                           +------------------------------------+
                           |            Base::bar()             |
                           +------------------------------------+

2.4.3 代码分析

通过实例直接调用虚函数

d.foo();

反汇编如下

=> 0x0000aaaaaaaab408 <+388>:   add x0, x29, #0xa8
   0x0000aaaaaaaab40c <+392>:   bl  0xaaaaaaaabbe4 <Derived::foo()>

通过基类指针调用虚函数

通过Base2基类指针访问d对象

  Base2 *pb2 = &d;
  pb2->foo();

反汇编如下

   0x0000aaaaaaaab398 <+276>:   add x0, x29, #0xa8
   0x0000aaaaaaaab39c <+280>:   str x0, [x29, #56]
   0x0000aaaaaaaab3a0 <+284>:   ldr x0, [x29, #56]
   
   0x0000aaaaaaaab3a4 <+288>:   ldr x0, [x0]
   
   0x0000aaaaaaaab3a8 <+292>:   add x0, x0, #0x10
   0x0000aaaaaaaab3ac <+296>:   ldr x1, [x0]
   
   0x0000aaaaaaaab3b0 <+300>:   ldr x0, [x29, #56]
   0x0000aaaaaaaab3b4 <+304>:   blr x1

通过Base3基类指针访问d对象

  Base3 *pb3 = &d;
  pb3->foo();

反汇编如下

   0x0000aaaaaaaab3c0 <+316>:   add x0, x29, #0xa8
   
   0x0000aaaaaaaab3c4 <+320>:   add x0, x0, #0x10
   0x0000aaaaaaaab3c8 <+324>:   str x0, [x29, #64]
   0x0000aaaaaaaab3cc <+328>:   ldr x0, [x29, #64]
   
   0x0000aaaaaaaab3d0 <+332>:   ldr x0, [x0]
   
   0x0000aaaaaaaab3d4 <+336>:   add x0, x0, #0x10
   0x0000aaaaaaaab3d8 <+340>:   ldr x1, [x0]
   
   0x0000aaaaaaaab3dc <+344>:   ldr x0, [x29, #64]
   0x0000aaaaaaaab3e0 <+348>:   blr x1

通过Base基类指针访问d对象

  pb = &d;
  pb->foo();

反汇编如下

   0x0000aaaaaaaab3e4 <+352>:   add x0, x29, #0xa8
   
   0x0000aaaaaaaab3e8 <+356>:   add x0, x0, #0x20
   0x0000aaaaaaaab3ec <+360>:   str x0, [x29, #48]
   0x0000aaaaaaaab3f0 <+364>:   ldr x0, [x29, #48]
   
   0x0000aaaaaaaab3f4 <+368>:   ldr x0, [x0]
   
   0x0000aaaaaaaab3f8 <+372>:   add x0, x0, #0x10
   0x0000aaaaaaaab3fc <+376>:   ldr x1, [x0]
   
   0x0000aaaaaaaab400 <+380>:   ldr x0, [x29, #48]
   0x0000aaaaaaaab404 <+384>:   blr x1

3. 总结

  • 普通类的对象模型主要由vptr(虚函数) + 当前类非静态成员变量组成
  • 单一继承类的对象模型主要由vptr + 基类非静态成员变量 + 派生类非静态成员变量组成
  • 虚继承的对象模型最复杂, 主要由多个vptr + 直接继承类非静态成员变量 + 派生类非静态成员变量 + 虚基类非静态成员变量组成

程序员自我修养(ID: dumphex)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容