4. 实现运行时多态的关键一步
  在造构函数里面设置好的vtable的值,显然,同一类型所有对象内的vtable值都是一样的,并且永远不会改变。下面是main函数生成的汇编代码,它展示了C++如何利用vtable来实现运行时多态。
.globl main
.type   main, @function
main:
.LFB3:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
pushl   %ebp
.cfi_def_cfa_offset 8
movl    %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
andl    $-16, %esp
subl    $32, %esp
leal    24(%esp), %eax
movl    %eax, (%esp)
call    _ZN6DeriveC1Ev
leal    24(%esp), %eax
movl    %eax, 28(%esp)
movl    28(%esp), %eax
movl    (%eax), %eax
movl    (%eax), %edx
movl    28(%esp), %eax
movl    %eax, (%esp)
call    *%edx
movl    $0, %eax
leave
ret
.cfi_endproc
andl    $-16, %esp
subl    $32, %esp
  这两句是为局部变量d和bp在堆栈上分配空间,也即如下的语句:
  Derive d;
  Base *pb;
  leal    24(%esp), %eax
  movl    %eax, (%esp)
  call    _ZN6DeriveC1Ev
  esp+24是变量d的首地址,先将它压到堆栈上,然后调用d的构造函数,相应翻译成C语言则如下:
  Derive::Dervice(&d);
  leal    24(%esp), %eax
  movl    %eax, 28(%esp)
  这里其实是将&d的值赋给pb,也即:
  pb = &d;
  关键的代码是下面这一段:
  movl    28(%esp), %eax
  movl    (%eax), %eax
  movl    (%eax), %edx
  movl    28(%esp), %eax
  movl    %eax, (%esp)
  call    *%edx
  翻译成C语言也传神的那句:
  pb->vtable[0](bp);
  编译器会记住f虚函数放在vtable的第0项,这是编译时信息。
  5. 小结
  这里省略了很多关于编译器和C++的细枝未节,是出于讨论方便用的需要。从上面的编译代码可以看到以下信息:
  1.每个类都有各有的vtable结构,编译会正确填写它们的虚函数表
  2. 对象在构造函数时,设置vtable值为该类的虚函数表
  3.在指针或者引用时调用虚函数,是通过object->vtable加上虚函数的offset来实现的。
  当然这仅仅是g++的实现方式,它和VC++的略有不同,但原理是一样的。