成都创新互联网站制作重庆分公司

从汇编的角度了解C++原理——虚函数-创新互联

文章目录
  • 1、虚函数
    • 1.1、虚函数储存结构
    • 1.2、子类重写虚函数
    • 1.3、在栈上调用虚函数
    • 1.4、在堆上调用虚函数(通过指针调用,多态)

本文用到的反汇编工具是objconv,使用方法可以看我另一篇文章https://blog.csdn.net/weixin_45001971/article/details/128660642。

成都创新互联公司自2013年创立以来,是专业互联网技术服务公司,拥有项目成都网站设计、成都做网站、外贸网站建设网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元府谷做网站,已为上家服务,为府谷各地企业和个人服务,联系电话:18980820575

其它文章:
从汇编的角度了解C++原理——类的储存结构和函数调用
从汇编的角度了解C++原理——new和malloc的区别
从汇编的角度了解C++原理——虚函数

1、虚函数 1.1、虚函数储存结构

在这里插入图片描述
反汇编。

main:
        sub     rsp, 56                             
        lea     rcx, [rsp+20H]                    
        call    ??0A@@QEAA@XZ      				//调用构造函数                  
        mov     eax, 4294967295                  
        add     rsp, 56                                
        ret                                             
        
??0A@@QEAA@XZ:									//调用A类的构造函数
        mov     qword [rsp+8H], rcx                  
        mov     rax, qword [rsp+8H]                    
        lea     rcx, [rel ??_7A@@6B@]           //获取虚表??_7A@@6B@的地址
        mov     qword [rax], rcx                //把虚表地址放在对象的头部      
        mov     rax, qword [rsp+8H]                    
        mov     dword [rax+8H], 10              //在对象首地址偏移8个字节的位置定义d1变量       
        mov     rax, qword [rsp+8H]                     
        ret                                                           

??_7A@@6B@:                     				//A类虚表                       
        dq ?func2@A@@UEAAXXZ                    //虚函数func2     

以上例的汇编代码可以得出带虚函数的类的储存结构如下图所示。
在这里插入图片描述
带有虚函数的对象的头部会放置8个字节大小的虚表地址,有了虚表之后的对象会以8个字节为单位去对齐,如上例中的A类,如果没有虚函数,它的大小为4个字节,而加了虚函数之后,大小变为了16个字节。

1.2、子类重写虚函数

在代码中添加A的子类B,重写func2方法。
在这里插入图片描述

反汇编

main:
        sub     rsp, 56                             
        lea     rcx, [rsp+20H]                       
        call    ??0B@@QEAA@XZ          		//调用B类构造                
        mov     eax, 4294967295                        
        add     rsp, 56                                
        ret                                             
        
??0A@@QEAA@XZ:								//A类构造函数
        mov     qword [rsp+8H], rcx                  
        mov     rax, qword [rsp+8H]                     
        lea     rcx, [rel ??_7A@@6B@]     	//把A类虚表的地址放在头部              
        mov     qword [rax], rcx                      
        mov     rax, qword [rsp+8H]                   
        mov     dword [rax+8H], 10                  
        mov     rax, qword [rsp+8H]           
        ret                                             

??0B@@QEAA@XZ:								//B类构造函数
        mov     qword [rsp+8H], rcx                
        sub     rsp, 40                             
        mov     rcx, qword [rsp+30H]               
        call    ??0A@@QEAA@XZ    			//调用A类构造                   
        mov     rax, qword [rsp+30H]             
        lea     rcx, [rel ??_7B@@6B@]       //把B类虚表的地址放在头部           
        mov     qword [rax], rcx                    
        mov     rax, qword [rsp+30H]                   
        add     rsp, 40                                 
        ret                                           
       
??_7A@@6B@:                         		//A类虚表                        
        dq ?func2@A@@UEAAXXZ                //A::func2                             
        dq ?func3@A@@UEAAXXZ                //A::func3      
             
??_7B@@6B@:                                 //B类虚表                       
        dq ?func2@B@@UEAAXXZ                //B::func2,被替换为了B实现的func2          
        dq ?func3@A@@UEAAXXZ                //A::func3                        

从该例中我们可以看到,父类有虚函数时,不光它自己有一张虚表,它的子子孙孙都会各带有一个自己的虚表,子类重写虚函数时,会把子类实现的函数指针替换上虚表,把原先父类的函数指针覆盖掉。

1.3、在栈上调用虚函数

在main里添加方法的调用。
在这里插入图片描述
反汇编。

main:
        sub     rsp, 56                               
        lea     rcx, [rsp+20H]                         
        call    ??0B@@QEAA@XZ                        
        lea     rcx, [rsp+20H]                        
        call    ?func1@A@@QEAAXXZ   		//调用A::func1                 
        lea     rcx, [rsp+20H]                        
        call    ?func2@B@@UEAAXXZ   		//调用B::func2                       
        lea     rcx, [rsp+20H]                         
        call    ?func3@A@@UEAAXXZ   		//调用A::func3                         
        mov     eax, 4294967295                         
        add     rsp, 56                                
        ret  

在栈上调用方法时,因为类型是确定的,所以编译器在编译阶段就会找到对应的函数去调用,调用过程与普通方法一样。

1.4、在堆上调用虚函数(通过指针调用,多态)

修改例程如下。
在这里插入图片描述
反汇编

main:
        sub     rsp, 72                                
        mov     ecx, 16                                
        call    ??2@YAPEAX_K@Z                         
        mov     qword [rsp+28H], rax                   
        cmp     qword [rsp+28H], 0                    
        jz      ?_001                                 
        mov     rcx, qword [rsp+28H]		//定义指针b                   
        call    ??0B@@QEAA@XZ                       
        mov     qword [rsp+30H], rax        //rsp+30H指向对象          
        jmp     ?_002                       //跳到?_002            

?_001:  mov     qword [rsp+30H], 0   
                  
?_002:  mov     rax, qword [rsp+30H]                   
        mov     qword [rsp+38H], rax        //rsp+38H指向对象         
        mov     rax, qword [rsp+38H]        //rax指向对象           
        mov     qword [rsp+20H], rax        //rsp+20H指向对象       
        mov     rcx, qword [rsp+20H]        //rcx指向对象          
        call    ?func1@A@@QEAAXXZ           //调用A::func1       
        mov     rax, qword [rsp+20H]                 
        mov     rax, qword [rax]            //取虚表        
        mov     rcx, qword [rsp+20H]                   
        call    near [rax]                  //执行虚表第一个函数,即B::func2           
        mov     rax, qword [rsp+20H]                   
        mov     rax, qword [rax]                       
        mov     rcx, qword [rsp+20H]                
        call    near [rax+8H]             	//执行虚表第二个函数,即A::func3             
        mov     eax, 4294967295                         
        add     rsp, 72                               
        ret                                          
        
??_7B@@6B@:                                           
        dq ?func2@B@@UEAAXXZ                        
        dq ?func3@A@@UEAAXXZ                         

从该例可以看到,通过指针来调用函数时。
如果是普通函数,编译器会直接根据指针类型,找到对应的的方法,而不是根据对象本身的类型,如本例中B类也实现了func1方法,但通过A类指针调用时,写到汇编里的时A::func1。
如果是虚函数,编译器不会根据名字来查找函数,而是让汇编代码通过虚表中的偏移量来调用,如本例中,b指针执行了func2和func3,这两个函数都没有被直接调用,而是以“call near [rax + 偏移量]”的形式调用了,这也是C++中父类指针指向子类对象的多态的实现原理。

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


名称栏目:从汇编的角度了解C++原理——虚函数-创新互联
新闻来源:http://cxhlcq.com/article/cdcoec.html

其他资讯

在线咨询

微信咨询

电话咨询

028-86922220(工作日)

18980820575(7×24)

提交需求

返回顶部