类的多继承大致可以分为两种情况.一种是无共同基类的.一种是有共同基类的.
先看一下第一种情况:
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. 当子类对象指针转换成基类指针,实际上是把子类对象的对应基类”隐含对象”地址赋值给基类指针.
对于有共同基类的多继承,也可以按照上面思路来探索.对于基类和子类有同名虚函数,也可以这样探索.
《coredump问题原理探究》Linux x86版6.7节多继承
原文地址:http://blog.csdn.net/xuzhina/article/details/43635239