网上已经有很多关于c++虚函数多态的文章,今天我们从汇编的角度来看下这种实现.
废话不说,直接上代码:
class BaseObject {
public:
void virtual print(int i,int j) {m_x = i;m_y = j;};
void virtual print2() { printf("base\n");};
private:
int m_x;
int m_y;
};
class Derived : public BaseObject {
public:
void virtual print2() { printf("child\n");};
void virtual print(int i,int j)
{
printf("m=%d y=%d\n",i,j);
};
};
int main(int argc,char *arg[])
{
BaseObject *obj = new BaseObject();
obj->print(100,1000);
obj = new Derived();
obj->print(100,20);
obj->print2();
return 0;
}
g++编译代码
g++ test.cpp -g -o test
进入gdb模式直接看main的汇编
(gdb) disassemble main
Dump of assembler code for function main(int, char**):
0x00000000000008de <+0>: push %rbp
0x00000000000008df <+1>: mov %rsp,%rbp
0x00000000000008e2 <+4>: push %rbx
0x00000000000008e3 <+5>: sub $0x28,%rsp
0x00000000000008e7 <+9>: mov %edi,-0x24(%rbp)
0x00000000000008ea <+12>: mov %rsi,-0x30(%rbp)
0x00000000000008ee <+16>: lea 0x289(%rip),%rdi # 0xb7e
0x00000000000008f5 <+23>: callq 0x790 <puts@plt>
0x00000000000008fa <+28>: mov $0x10,%edi
0x00000000000008ff <+33>: callq 0x780 <_Znwm@plt>
0x0000000000000904 <+38>: mov %rax,%rbx
0x0000000000000907 <+41>: movq $0x0,(%rbx)
0x000000000000090e <+48>: movl $0x0,0x8(%rbx)
0x0000000000000915 <+55>: movl $0x0,0xc(%rbx)
0x000000000000091c <+62>: mov %rbx,%rdi
0x000000000000091f <+65>: callq 0xa8e <BaseObject::BaseObject()>
0x0000000000000924 <+70>: mov %rbx,-0x18(%rbp)
0x0000000000000928 <+74>: mov -0x18(%rbp),%rax
0x000000000000092c <+78>: mov (%rax),%rax
0x000000000000092f <+81>: mov (%rax),%rax
0x0000000000000932 <+84>: mov -0x18(%rbp),%rcx
0x0000000000000936 <+88>: mov $0x3e8,%edx
0x000000000000093b <+93>: mov $0x64,%esi
0x0000000000000940 <+98>: mov %rcx,%rdi
0x0000000000000943 <+101>: callq *%rax
0x0000000000000945 <+103>: mov $0x10,%edi
0x000000000000094a <+108>: callq 0x780 <_Znwm@plt>
0x000000000000094f <+113>: mov %rax,%rbx
0x0000000000000952 <+116>: movq $0x0,(%rbx)
0x0000000000000959 <+123>: movl $0x0,0x8(%rbx)
0x0000000000000960 <+130>: movl $0x0,0xc(%rbx)
0x0000000000000967 <+137>: mov %rbx,%rdi
0x000000000000096a <+140>: callq 0xaa8 <Derived::Derived()>
0x000000000000096f <+145>: mov %rbx,-0x18(%rbp)
---Type <return> to continue, or q <return> to quit---
0x0000000000000973 <+149>: mov -0x18(%rbp),%rax
0x0000000000000977 <+153>: mov (%rax),%rax
0x000000000000097a <+156>: mov (%rax),%rax
0x000000000000097d <+159>: mov -0x18(%rbp),%rcx
0x0000000000000981 <+163>: mov $0x14,%edx
0x0000000000000986 <+168>: mov $0x64,%esi
0x000000000000098b <+173>: mov %rcx,%rdi
0x000000000000098e <+176>: callq *%rax
0x0000000000000990 <+178>: mov -0x18(%rbp),%rax
0x0000000000000994 <+182>: mov (%rax),%rax
0x0000000000000997 <+185>: add $0x8,%rax
0x000000000000099b <+189>: mov (%rax),%rax
0x000000000000099e <+192>: mov -0x18(%rbp),%rdx
0x00000000000009a2 <+196>: mov %rdx,%rdi
0x00000000000009a5 <+199>: callq *%rax
0x00000000000009a7 <+201>: mov $0x0,%eax
0x00000000000009ac <+206>: add $0x28,%rsp
0x00000000000009b0 <+210>: pop %rbx
0x00000000000009b1 <+211>: pop %rbp
0x00000000000009b2 <+212>: retq
End of assembler dump.
(gdb)
我们看虚函数的调用,着重看下面的这段
0x0000000000000990 <+178>: mov -0x18(%rbp),%rax
0x0000000000000994 <+182>: mov (%rax),%rax
0x0000000000000997 <+185>: add $0x8,%rax
0x000000000000099b <+189>: mov (%rax),%rax
0x000000000000099e <+192>: mov -0x18(%rbp),%rdx
0x00000000000009a2 <+196>: mov %rdx,%rdi
0x00000000000009a5 <+199>: callq *%rax
第一行汇编, -0x18(%rbp) 指向(这里其实是指针的指针)的内容,就是obj = new Derived()的内存对象地址.
0x0000000000000990 <+178>: mov -0x18(%rbp),%rax #把对象地址放入rax寄存器.此时rax寄存器中的值是obj对象的地址.
0x0000000000000994 <+182>: mov (%rax),%rax #取出对象地址的首地址的内容,即为虚函数表的位置
0x0000000000000997 <+185>: add $0x8,%rax #把虚函数表首地址+8 (第二个虚函数) 后放入rax,此时rax中存放的就是指向虚函数的指针
0x000000000000099b <+189>: mov (%rax),%rax #虚函数指针