C++虚函数调用过程深度理解

前言

笔者在前一篇文章 理解C++菱形继承 中分享了一些关于cpp菱形继承的理解。但是对于cpp虚函数的理解还有些许的模糊,知道一些内容,但没有完全详尽。

接下来笔者将根据上篇文章的部分示例代码验证一些关于虚函数调用以及类调用的一些深度理解。

概念理解

笔者的理解如下:

  • cpp虚函数地址存在于虚函数表中,含有虚函数的类其内部默认存在一个虚函数表指针,所以:这种类的内存占用大小= 虚函数表指针占用内存大小 + 数据成员占用内存大小 + 内存对齐增加的内存大小
    • 这个原理,其实笔者是知道的。但是,笔者对于类对象地址虚函数表指针虚函数地址之间如何转换的有一点模糊。这将在后续验证。
  • cpp对象函数调用时,this指针将作为第一个函数参数传递到函数中。这是编译器的默认实现,并不会显式体现出来。
    • 思考ccpp函数的区别,即调用对象函数该如何实现。

验证

将上篇文章中的部分示例代码调试反汇编后,按步详细解析汇编代码,便对上面的理解有了完整清晰的理解。原部分cpp代码:

int main(int argc, char* argv[]) {
    DerivedB *d = new DiamondDerived();
    
    d->VirtualFunc("called_from_main");

    delete d;

    return 0;
}

可以在软件visual studio中点击调试窗口反汇编

以下是反汇编的片段,笔者已按步注释,直接看重点:

  • 汇编常识
    • rax寄存器一般存储返回值。
    • rcx rdx寄存器一般存储入参。
    • rbp栈底指针寄存器,rsp栈顶指针寄存器。

反汇编解析:


d->VirtualFunc("called_from_main");
;; 下面是上面这句cpp语句对应的汇编代码

; 取字符串常量call_from_main的地址给rdx寄存器
00007FF7AAFFAEA0  lea         rdx,[string "called_from_main" (07FF7AB000438h)] 

; 将取栈底偏移128字节的内存地址给rcx寄存器,即临时变量,当入参传递给下列的basic_string构造函数。
; 其实就是临时变量string("called_from_main")的初始化,这是它的内存地址,可看做它的this指针。
00007FF7AAFFAEA7  lea         rcx,[rbp+128h]  

; 调用basic_string构造函数
00007FF7AAFFAEAE  call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> > (07FF7AAFF12C6h)  

; nop指令,不做任何操作,占位,用途是内存对齐。
00007FF7AAFFAEB3  nop  

; 取内存地址d的8字节数据给rax寄存器
00007FF7AAFFAEB4  mov         rax,qword ptr [d]  
; 取rax存储值对应的地址的8字节数据给rax寄存器
00007FF7AAFFAEB8  mov         rax,qword ptr [rax]  

; 上面这两部理解为:
; 第一步先将内存地址d存储的8字节数据转换为this指针,赋值给rax。
; 第二部将this指针指向的8字节数据转换为另一个指针,其实是虚函数表指针。

; 取栈底往上偏移128字节对应的地址赋值给rdx
00007FF7AAFFAEBB  lea         rdx,[rbp+128h]

; 取内存地址d的8字节数据给rcx,即rcx存储this指针
00007FF7AAFFAEC2  mov         rcx,qword ptr [d]  

; 调用rax地址偏移8字节对应内存的函数,就是调用虚函数表指针偏移8字节后的函数,即某个虚函数
00007FF7AAFFAEC6  call        qword ptr [rax+8]  

; 占位符,内存对齐。
00007FF7AAFFAEC9  nop  

; 将栈底偏移128字节的内存地址赋值给rcx。
; 即前面的临时变量。作为下面调用basic_string析构函数的this指针。
00007FF7AAFFAECA  lea         rcx,[rbp+128h]  

; 调用basic_string的析构函数,释放临时变量
00007FF7AAFFAED1  call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::~basic_string<char,std::char_traits<char>,std::allocator<char> > (07FF7AAFF1136h)  

结尾

  • 行文至此,关于虚函数调用过程深度详解的分享就结束了。希望能帮助到你。
  • 行文如有错误,欢迎指正。
  • 如果觉得还不错,你的点赞关注加收藏便是笔者继续更新的最大动力哦^_^
  • 关联文章感兴趣可移步。
  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值