《coredump问题原理探究》Linux x86版6.7节多继承

类的多继承大致可以分为两种情况.一种是无共同基类的.一种是有共同基类的.

技术分享技术分享

先看一下第一种情况:

  1 #include <stdio.h>
  2 class xuzhina_dump_c06_s5_mother
  3 {
  4     private:
  5         int m_age;
  6         int m_beauty;
  7     public:
  8         virtual void print()
  9         {
 10             printf( "mother\n" );
 11         }
 12 
 13         virtual void setBeauty( int age, int beauty )
 14         {
 15             m_age = age - 5;
 16             m_beauty = beauty;
 17         }
 18 };
19 
 20 class xuzhina_dump_c06_s5_father
 21 {
 22     private:
 23         int m_strong;
 24         int m_age;
 25     public:
 26         virtual void print()
 27         {
 28             printf( "father\n" );
 29         }
 30         virtual void setStrong( int strong, int age )
 31         {
 32             m_strong = strong;
 33             m_age = age;
 34         }
 35 };
 36
37 class xuzhina_dump_c06_s5_child: public xuzhina_dump_c06_s5_father,
 38     public xuzhina_dump_c06_s5_mother
 39 
 40 {
 41     private:
 42         bool m_newMind;
 43     public:
 44         virtual void print()
 45         {
 46             printf( "child\n" );
 47         }
 48 
 49         virtual void setGender( bool gender )
 50         {
 51             m_newMind = true;
 52             if ( gender )
 53             {
 54                 setBeauty( 10, 10 );
 55             }
 56             else
 57             {
 58                 setStrong( 20,20 );
 59             }
 60         }
 61 };
 62
63 int main()
 64 {
 65     xuzhina_dump_c06_s5_child* child = new xuzhina_dump_c06_s5_child;
 66     child->setGender( false );
 67     child->print();
 68 
 69     xuzhina_dump_c06_s5_father* f = child;
 70     f->print();
 71 
 72     xuzhina_dump_c06_s5_mother* m = child;
 73     m->print();
 74 
 75     return 0;
 76 }


看一下main函数的汇编:

(gdb) disassemble main
Dump of assembler code for function main:
   0x080485b0 <+0>:     push   %ebp
   0x080485b1 <+1>:     mov    %esp,%ebp
   0x080485b3 <+3>:     push   %ebx
   0x080485b4 <+4>:     and    $0xfffffff0,%esp
   0x080485b7 <+7>:     sub    $0x20,%esp
   0x080485ba <+10>:    movl   $0x1c,(%esp)
   0x080485c1 <+17>:    call   0x8048490 <_Znwj@plt>
   0x080485c6 <+22>:    mov    %eax,%ebx
   0x080485c8 <+24>:    mov    %ebx,(%esp)
   0x080485cb <+27>:    call   0x8048746 <_ZN25xuzhina_dump_c06_s5_childC2Ev>
   0x080485d0 <+32>:    mov    %ebx,0x1c(%esp)
   0x080485d4 <+36>:    mov    0x1c(%esp),%eax
   0x080485d8 <+40>:    mov    (%eax),%eax
   0x080485da <+42>:    add    $0x8,%eax
   0x080485dd <+45>:    mov    (%eax),%eax
   0x080485df <+47>:    movl   $0x0,0x4(%esp)
   0x080485e7 <+55>:    mov    0x1c(%esp),%edx
   0x080485eb <+59>:    mov    %edx,(%esp)
   0x080485ee <+62>:    call   *%eax
   0x080485f0 <+64>:    mov    0x1c(%esp),%eax
   0x080485f4 <+68>:    mov    (%eax),%eax
   0x080485f6 <+70>:    mov    (%eax),%eax
   0x080485f8 <+72>:    mov    0x1c(%esp),%edx
   0x080485fc <+76>:    mov    %edx,(%esp)
   0x080485ff <+79>:    call   *%eax
   0x08048601 <+81>:    mov    0x1c(%esp),%eax
   0x08048605 <+85>:    mov    %eax,0x18(%esp)
   0x08048609 <+89>:    mov    0x18(%esp),%eax
   0x0804860d <+93>:    mov    (%eax),%eax
   0x0804860f <+95>:    mov    (%eax),%eax
   0x08048611 <+97>:    mov    0x18(%esp),%edx
   0x08048615 <+101>:   mov    %edx,(%esp)
   0x08048618 <+104>:   call   *%eax
   0x0804861a <+106>:   cmpl   $0x0,0x1c(%esp)
   0x0804861f <+111>:   je     0x804862a <main+122>
   0x08048621 <+113>:   mov    0x1c(%esp),%eax
   0x08048625 <+117>:   add    $0xc,%eax
   0x08048628 <+120>:   jmp    0x804862f <main+127>
   0x0804862a <+122>:   mov    $0x0,%eax
   0x0804862f <+127>:   mov    %eax,0x14(%esp)
   0x08048633 <+131>:   mov    0x14(%esp),%eax
   0x08048637 <+135>:   mov    (%eax),%eax
   0x08048639 <+137>:   mov    (%eax),%eax
   0x0804863b <+139>:   mov    0x14(%esp),%edx
   0x0804863f <+143>:   mov    %edx,(%esp)
   0x08048642 <+146>:   call   *%eax
   0x08048644 <+148>:   mov    $0x0,%eax
   0x08048649 <+153>:   mov    -0x4(%ebp),%ebx
   0x0804864c <+156>:   leave  
   0x0804864d <+157>:   ret    
End of assembler dump.


由上面的汇编,可以看到,对象child的地址存放在esp+0x1c

而下面这几条指令

   0x08048601 <+81>:    mov    0x1c(%esp),%eax
   0x08048605 <+85>:    mov    %eax,0x18(%esp)
   0x08048609 <+89>:    mov    0x18(%esp),%eax
   0x0804860d <+93>:    mov    (%eax),%eax
   0x0804860f <+95>:    mov    (%eax),%eax
   0x08048611 <+97>:    mov    0x18(%esp),%edx
   0x08048615 <+101>:   mov    %edx,(%esp)
   0x08048618 <+104>:   call   *%eax

   0x08048621 <+113>:   mov    0x1c(%esp),%eax
   0x08048625 <+117>:   add    $0xc,%eax
   0x0804862f <+127>:   mov    %eax,0x14(%esp)
   0x08048633 <+131>:   mov    0x14(%esp),%eax
   0x08048637 <+135>:   mov    (%eax),%eax
   0x08048639 <+137>:   mov    (%eax),%eax
   0x0804863b <+139>:   mov    0x14(%esp),%edx
   0x0804863f <+143>:   mov    %edx,(%esp)
   0x08048642 <+146>:   call   *%eax


由于是和代码

69     xuzhina_dump_c06_s5_father* f = child;
 70     f->print();
 71 
 72     xuzhina_dump_c06_s5_mother* m = child;
 73     m->print();


相对应的.

   0x08048625 <+117>:   add    $0xc,%eax

可以看到非常奇怪的现象,当类xuzhina_dump_c06_s5_child的指针转换成类xuzhina_dump_c06_s5-_mother的指针时,并不是直接赋值过去,而是比预料的地址加了一个偏移值.

那么这个偏移值0xC是怎么来的呢?根据上一节的经验,看一下构造函数的汇编:

(gdb) disassemble _ZN25xuzhina_dump_c06_s5_childC2Ev
Dump of assembler code for function _ZN25xuzhina_dump_c06_s5_childC2Ev:
   0x08048746 <+0>:     push   %ebp
   0x08048747 <+1>:     mov    %esp,%ebp
   0x08048749 <+3>:     sub    $0x18,%esp
   0x0804874c <+6>:     mov    0x8(%ebp),%eax
   0x0804874f <+9>:     mov    %eax,(%esp)
   0x08048752 <+12>:    call   0x804872a <_ZN26xuzhina_dump_c06_s5_fatherC2Ev>
   0x08048757 <+17>:    mov    0x8(%ebp),%eax
   0x0804875a <+20>:    add    $0xc,%eax
   0x0804875d <+23>:    mov    %eax,(%esp)
   0x08048760 <+26>:    call   0x8048738 <_ZN26xuzhina_dump_c06_s5_motherC2Ev>
   0x08048765 <+31>:    mov    0x8(%ebp),%eax
   0x08048768 <+34>:    movl   $0x8048848,(%eax)
   0x0804876e <+40>:    mov    0x8(%ebp),%eax
   0x08048771 <+43>:    movl   $0x804885c,0xc(%eax)
   0x08048778 <+50>:    leave  
   0x08048779 <+51>:    ret    
End of assembler dump.


由上面,类xuzhina_dump_c06_s5_child的对象实际就是两个类xuzhina_dump_c06_s5_father,xuzhina_dump_c06_s5_mother的对象组合,偏移值0xC刚好是类xuzhina_dump_c06_s5_father的大小(虚函数表指针+两个成员变量m_strong,m_age).

且由0x08048768, 0x08048771两条指令来看,类xuzhina_dump_c06_s5_child存放两个虚函数表,分别存放在0x8048848, 0x804885c.

看一下这两个表分别存放着什么东西.

第一个:

(gdb) x /4x 0x8048848
0x8048848 <_ZTV25xuzhina_dump_c06_s5_child+8>:  0x080486a8      0x08048690      0x080486c4      0xfffffff4
(gdb) info symbol 0x080486a8
xuzhina_dump_c06_s5_child::print() in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5
(gdb) info symbol 0x08048690
xuzhina_dump_c06_s5_father::setStrong(int, int) in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5
(gdb) info symbol 0x080486c4
xuzhina_dump_c06_s5_child::setGender(bool) in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5

第二个:

(gdb) x /4x 0x804885c
0x804885c <_ZTV25xuzhina_dump_c06_s5_child+28>: 0x080486bc      0x08048662      0x00000000      0x00000000
(gdb) info symbol 0x080486bc
non-virtual thunk to xuzhina_dump_c06_s5_child::print() in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5
(gdb) info symbol 0x08048662
xuzhina_dump_c06_s5_mother::setBeauty(int, int) in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5
(gdb) disassemble 0x080486bc
Dump of assembler code for function _ZThn12_N25xuzhina_dump_c06_s5_child5printEv:
   0x080486bc <+0>:     subl   $0xc,0x4(%esp)
   0x080486c1 <+5>:     jmp    0x80486a8 <_ZN25xuzhina_dump_c06_s5_child5printEv>
End of assembler dump. 

从上面看,类xuzhina_dump_c06_s5_child的对象内存分布如下:

技术分享

由上可知,在多继承情况下,有这样的结论:

1.     有多个虚函数表.

2.     类对象的大小就是等于各个基类的大小(虚函数表指针+成员变量)与自身成员变量之和.

3.     各个基类在子类里的”隐含对象”顺序是按照继承顺序来排列,和基类的声明/定义顺序无关.

4.     当子类对象指针转换成基类指针,实际上是把子类对象的对应基类”隐含对象”地址赋值给基类指针.

 

对于有共同基类的多继承,也可以按照上面思路来探索.对于基类和子类有同名虚函数,也可以这样探索.


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。