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

本文用到的反汇编工具是objconv,使用方法可以看我另一篇文章https://blog.csdn.net/weixin_45001971/article/details/128660642
其它文章:
从汇编的角度了解C++原理——类的储存结构和函数调用
从汇编的角度了解C++原理——new和malloc的区别
从汇编的角度了解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对象储存结构的示意图。
在这里插入图片描述

1.2.1、验证

对例程做以下修改。
在这里插入图片描述
输出
在这里插入图片描述
与汇编里显示的一致。

2、子类的储存结构

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

2.1、单继承

修改上一节的例程如下。
在这里插入图片描述
反汇编得到。

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个字节的位置,如下图所示。
在这里插入图片描述

2.2、多继承

修改上一节的例程如下。
在这里插入图片描述

反汇编,我们直接看到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类的构造函数可以得出,多继承的子类储存结构跟单继承是差不多的,按照父类的顺序一个接一个排布下去。
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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、付费专栏及课程。

余额充值