从汇编的角度了解C++原理——类的储存结构和函数调用

文章目录

  • 1、类的储存结构和函数调用
    • 1.1、函数执行过程
    • 1.2、类的基本储存结构
      • 1.2.1、验证
  • 2、子类的储存结构
    • 2.1、单继承
    • 2.2、多继承

本文用到的反汇编工具是objconv,使用方法可以看我另一篇文章https://blog.csdn.net/weixin_45001971/article/details/128660642。
其它文章:
从汇编的角度了解C++原理——类的储存结构和函数调用
从汇编的角度了解C++原理——new和malloc的区别
从汇编的角度了解C++原理——虚函数

1、类的储存结构和函数调用

以这段代码为例。
从汇编的角度了解C++原理——类的储存结构和函数调用_第1张图片
编译后对obj文件反汇编,得到以下汇编代码,配合常量的值来分析汇编的含义。

main:
        sub     rsp, 72                                
        mov     dword [rsp+20H], 10    //定义变量num,地址为rsp+20H
        lea     rcx, [rsp+28H]         //定义对象a,地址为rsp+28H,把地址存入rcx寄存器               
        call    ??0A@@QEAA@XZ          //调用A类构造函数               
        mov     eax, 4294967295        //4294967295就是-1,eax寄存器用来存放返回值           
        add     rsp, 72                                
        ret                                           

??0A@@QEAA@XZ:						   //A类构造函数
		/* 从rcx寄存器中取执行构造的对象,放到rsp+8H */
        mov     qword [rsp+8H], rcx              
        
        /* 初始化变量d1,地址是对象的首地址 */   
        mov     rax, qword [rsp+8H]      
        mov     dword [rax], 10        
                     
        /* 初始化变量d2,地址是对象的首地址+4H */   
        mov     rax, qword [rsp+8H]                   
        mov     byte [rax+4H], 20           
         
        /* 初始化变量d3,地址是对象的首地址+8H */            
        mov     rax, qword [rsp+8H]                   
        mov     dword [rax+8H], 30     
          
        /* 初始化变量d4,地址是对象的首地址+0CH */              
        mov     rax, qword [rsp+8H]                    
        mov     byte [rax+0CH], 40    
                    
        mov     rax, qword [rsp+8H]                   
        ret                                  
; ??0A@@QEAA@XZ End of function

从这个例子中分析可以了解到函数的执行过程和类的基本储存结构。

1.1、函数执行过程

执行对象的函数过程分为以下两步:
1、把对象的地址放入rcx寄存器。
2、执行函数。执行函数时,如果需要访问对象的成员,会先从rcx寄存器拿到对象的首地址,然后通过地址偏移访问到变量。

1.2、类的基本储存结构

类的对象的成员是按照定义的顺序从对象首地址依次排列下去的,在本例中,类成员最大的是int类型,所以按4个字节的规则来对齐,下图是本例a对象储存结构的示意图。
从汇编的角度了解C++原理——类的储存结构和函数调用_第2张图片

1.2.1、验证

对例程做以下修改。
从汇编的角度了解C++原理——类的储存结构和函数调用_第3张图片
输出
从汇编的角度了解C++原理——类的储存结构和函数调用_第4张图片
与汇编里显示的一致。

2、子类的储存结构

在C++的继承中,子类也可以定义变量,这时候又是怎么储存变量的呢?下面分别介绍单继承和多继承的储存结构。

2.1、单继承

修改上一节的例程如下。
从汇编的角度了解C++原理——类的储存结构和函数调用_第5张图片
反汇编得到。

main:
        sub     rsp, 72                     
        lea     rcx, [rsp+20H]       	                 
        call    ??0B@@QEAA@XZ           //调用B类构造           
        lea     rcx, [rsp+20H]                       
        call    ?func1@A@@QEAAXXZ       //调用A类的func1    
        lea     rcx, [rsp+20H]                       
        call    ?func2@A@@QEAAXXZ       //调用A类的func2              
        mov     eax, 4294967295                        
        add     rsp, 72                                
        ret  
                                                  
??0A@@QEAA@XZ:							//A类的构造函数,与上一节例子一样
        mov     qword [rsp+8H], rcx                   
        mov     rax, qword [rsp+8H]                  
        mov     dword [rax], 10                 
        mov     rax, qword [rsp+8H]                    
        mov     byte [rax+4H], 20                      
        mov     rax, qword [rsp+8H]                    
        mov     dword [rax+8H], 30                     
        mov     rax, qword [rsp+8H]                    
        mov     byte [rax+0CH], 40                     
        mov     rax, qword [rsp+8H]                     
        ret                                             
        
??0B@@QEAA@XZ:							//B类的构造函数
        mov     qword [rsp+8H], rcx     //对象首地址放到rsp+8H
        sub     rsp, 40                 //压栈      
        mov     rcx, qword [rsp+30H]   	//结合上一条指令等价于rsp-40+48,也就是取对象首地址            
        call    ??0A@@QEAA@XZ           //调用A类构造          
        mov     rax, qword [rsp+30H]                   
        mov     dword [rax+10H], 50     //对象首地址往后偏移16字节的位置定义为d5               
        mov     rax, qword [rsp+30H]                  
        add     rsp, 40                 //出栈              
        ret                                           

?func1@A@@QEAAXXZ:; Function begin
        mov     qword [rsp+8H], rcx                    
        mov     rax, qword [rsp+8H]                 
        mov     dword [rax], 11                         
        ret  
                                                   
?func2@A@@QEAAXXZ:; Function begin
        mov     qword [rsp+8H], rcx                 
        mov     rax, qword [rsp+8H]                    
        mov     dword [rax], 12                        
        ret                                           

从示例中可以看到,子类中定义的成员,是拼接在父类成员的后面的,A类的大小是16字节,B类的成员d5被定义在this指针往后偏移16个字节的位置,如下图所示。
从汇编的角度了解C++原理——类的储存结构和函数调用_第6张图片

2.2、多继承

修改上一节的例程如下。
从汇编的角度了解C++原理——类的储存结构和函数调用_第7张图片

反汇编,我们直接看到C类的构造函数。

??0C@@QEAA@XZ:
        mov     qword [rsp+8H], rcx                    
        sub     rsp, 40                  	//rsp指针减40              
        mov     rcx, qword [rsp+30H]    	//rsp+30H取的是首地址,因为上一条指令减40(rsp-40+30H 等价第一句的 rsp+8H)   
        call    ??0A@@QEAA@XZ               //在首地址初始化A类          
        mov     rax, qword [rsp+30H]                  
        add     rax, 8                      //指针基于首地址往后偏移8字节         
        mov     rcx, rax                               
        call    ??0B@@QEAA@XZ               //在首地址+8 初始化B类            
        mov     rax, qword [rsp+30H]                 
        mov     dword [rax+0CH], 3          //在首地址+0CH 处初始化C类            
        mov     rax, qword [rsp+30H]                   
        add     rsp, 40                                
        ret                                        

通过C类的构造函数可以得出,多继承的子类储存结构跟单继承是差不多的,按照父类的顺序一个接一个排布下去。
从汇编的角度了解C++原理——类的储存结构和函数调用_第8张图片

你可能感兴趣的:(C++,c++,java,开发语言)