c++ 从汇编代码看虚函数实现

网上已经有很多关于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          #虚函数指针


   

C++ 中,虚函数是通过虚函数表来实现的。每个对象都有一个指向虚函数表的指针,虚函数表是一个数组,存储了该对象的虚函数的地址。 当调用一个虚函数时,编译器会先查找该对象的虚函数表,然后根据虚函数的索引找到对应的函数地址,最终调用该函数。 以下是一个简单的示例,展示了虚函数汇编代码实现: ```c++ class Base { public: virtual void foo() { printf("Base::foo()\n"); } }; class Derived : public Base { public: virtual void foo() { printf("Derived::foo()\n"); } }; int main() { Base* ptr = new Derived(); ptr->foo(); delete ptr; return 0; } ``` 对应的汇编代码如下(采用 AT&T 语法): ```asm .file "main.cpp" .section .text .globl main .p2align 4,,15 .type main, @function main: .LFB0: .cfi_startproc subq $8, %rsp movl $8, %edi call operator new(unsigned long) movq %rax, %rdi leaq .LC0(%rip), %rsi movl $1, %edx movl $0, %eax call __printf_chk movq %rax, %rdi movq %rax, -8(%rbp) movq $vtable for Derived(%rip), %rax movq (%rax), %rax movq (%rax), %rax movq -8(%rbp), %rdx movq %rdx, %rsi movq %rax, (%rsp) call *%rax leaq -8(%rbp), %rax movq (%rax), %rax movq %rax, (%rsp) call operator delete(void*) xorl %eax, %eax addq $8, %rsp .cfi_endproc .LFE0: .size main, .-main .section .rodata .align 8 .LC0: .string "Base::foo()\n" .section .rodata.cst4 .align 4 vtable for Derived: .quad 0 .quad typeinfo for Derived .quad Derived::foo() .section .note.GNU-stack,"",@progbits ``` 可以看到,在调用虚函数时,程序首先通过虚函数表找到对应的函数地址,然后通过 `call` 指令调用该函数。虚函数表的地址是通过 `vtable for Derived(%rip)` 获取的。调用完毕后,还需要调用 `operator delete` 释放内存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值