千家信息网

C++中的虚函数如何用

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,本篇内容主要讲解"C++中的虚函数如何用",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"C++中的虚函数如何用"吧!虚函数调用属于运行时多态,在类的继承关系
千家信息网最后更新 2026年11月02日C++中的虚函数如何用

本篇内容主要讲解"C++中的虚函数如何用",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"C++中的虚函数如何用"吧!

虚函数调用属于运行时多态,在类的继承关系中,通过父类指针来调用不同子类对象的同名方法,而产生不同的效果。

C++ 中的多态是通过晚绑定(对象构造时)来实现的。

用法

在函数之前声明关键字 virtual 表示这是一个虚函数,在函数后增加一个 = 0 表示这是一个纯虚函数,纯虚函数的类不能创建具体实例。

该示例作后文分析使用,一个包含纯虚函数的父类,一个重写了父类方法的子类,一个无继承的类。

struct Base {  Base() : val(7777) {}  virtual int fuck(int a) = 0;  int val;};struct Der : public Base {  Der() = default;  int fuck(int a) override { return val + 4396; }};struct A {  A() = default;  void funny(int a) {}};int main() {  Der der;  Base *pbase = &der;  pbase->fuck(sizeof(Der)); // 调用 Der::fuck(int a);  A a;  a.funny(sizeof(A)); // A::funny(int a);  return 3;}

实现

原来就了解虚函数是通过虚表的偏移来获取实际调用函数地址来实现的,但是在何时确定这个偏移和具体的偏移细节也没有说明,今儿个来探探究竟。

拿上面的代码进行反汇编获提取部分函数,main,Base::Base(), Base::fuck(), Der::Der(), Der::fuck, A::funny() 如下:

_ZN4BaseC2Ev:.LFB1:  .cfi_startproc  pushq  %rbp  .cfi_def_cfa_offset 16  .cfi_offset 6, -16  movq  %rsp, %rbp  .cfi_def_cfa_register 6  movq  %rdi, -8(%rbp)  // 还是 main 函数的栈帧 -32(%rpb) 的地址  leaq  16+_ZTV4Base(%rip), %rdx // 关键点来了,取虚表偏移 16 的地址也就是 __cxa_pure_virtual,这里是没有意义的  movq  -8(%rbp), %rax  movq  %rdx, (%rax)   // 将 __cxa_pure_virtual 的地址存放在 地址rax 的内存中(这个例子中也就是main 函数的栈帧 -32(%rpb) 的地方),   movq  -8(%rbp), %rax  // 然后往后偏移 8 个字节,也就是跳过虚表指针,对成员变量 val 初始化。  movl  $7777, 8(%rax)  nop           // 注:上面是用这个示例中实际的地址带入的,实际上对于一个有的类的处理是一个通用逻辑的,构造函数传入的第一个参数 rdi 是 this 指针,由于有虚表存在的影响,这里会修改 this 指针所在地址的内容,也就是虚表的偏移地址(非起始地址)  popq  %rbp  .cfi_def_cfa 7, 8  ret  .cfi_endproc.LFE1:  .size  _ZN4BaseC2Ev, .-_ZN4BaseC2Ev  .weak  _ZN4BaseC1Ev  .set  _ZN4BaseC1Ev,_ZN4BaseC2Ev  .section  .text._ZN3Der4fuckEi,"axG",@progbits,_ZN3Der4fuckEi,comdat  .align 2  .weak  _ZN3Der4fuckEi  .type  _ZN3Der4fuckEi, @function_ZN3Der4fuckEi:.LFB3:  .cfi_startproc  pushq  %rbp  .cfi_def_cfa_offset 16  .cfi_offset 6, -16  movq  %rsp, %rbp  .cfi_def_cfa_register 6  movq  %rdi, -8(%rbp)  movl  %esi, -12(%rbp)  movq  -8(%rbp), %rax  movl  8(%rax), %eax  // 成员变量 val,val 是从 rdi 中偏移 8 字节取的值  addl  $4396, %eax   // val + 4396  popq  %rbp  .cfi_def_cfa 7, 8  ret  .cfi_endproc.LFE3:  .size  _ZN3Der4fuckEi, .-_ZN3Der4fuckEi  .section  .text._ZN1A5funnyEi,"axG",@progbits,_ZN1A5funnyEi,comdat  .align 2  .weak  _ZN1A5funnyEi  .type  _ZN1A5funnyEi, @function_ZN1A5funnyEi:.LFB4:  .cfi_startproc  pushq  %rbp  .cfi_def_cfa_offset 16  .cfi_offset 6, -16  movq  %rsp, %rbp  .cfi_def_cfa_register 6  movq  %rdi, -8(%rbp)  movl  %esi, -12(%rbp)  nop  popq  %rbp  .cfi_def_cfa 7, 8  ret  .cfi_endproc.LFE4:  .size  _ZN1A5funnyEi, .-_ZN1A5funnyEi  .section  .text._ZN3DerC2Ev,"axG",@progbits,_ZN3DerC5Ev,comdat  .align 2  .weak  _ZN3DerC2Ev  .type  _ZN3DerC2Ev, @function_ZN3DerC2Ev:.LFB7:  .cfi_startproc  pushq  %rbp  .cfi_def_cfa_offset 16  .cfi_offset 6, -16  movq  %rsp, %rbp  .cfi_def_cfa_register 6  subq  $16, %rsp  movq  %rdi, -8(%rbp)  // rdi 是取的 main 栈帧 -32(%rbp) 的地址  movq  -8(%rbp), %rax  movq  %rax, %rdi  call  _ZN4BaseC2Ev   // Base 的构造函数,并且又把传进来的参数作为实参传进去了,这里跟踪进去  leaq  16+_ZTV3Der(%rip), %rdx // 取虚表偏移16字节 _ZN3Der4fuckEi 的地址   movq  -8(%rbp), %rax  movq  %rdx, (%rax)   // rax 在之前的 Base构造函数中是被修改了的,这里将继续修改内容,前一次的修改失效。  nop  leave  .cfi_def_cfa 7, 8  ret  .cfi_endproc.LFE7:  .size  _ZN3DerC2Ev, .-_ZN3DerC2Ev  .weak  _ZN3DerC1Ev  .set  _ZN3DerC1Ev,_ZN3DerC2Ev  .text  .globl main  .type  main, @functionmain:.LFB5:  .cfi_startproc  pushq  %rbp  .cfi_def_cfa_offset 16  .cfi_offset 6, -16  movq  %rsp, %rbp  .cfi_def_cfa_register 6  subq  $48, %rsp  leaq  -32(%rbp), %rax // 取 -32(%rbp) 的地址,对应 Base *pbase;  movq  %rax, %rdi  call  _ZN3DerC1Ev   // 调用了构造函数,并且以-32(%rbp) 的地址作为参数,这里跟踪进去  leaq  -32(%rbp), %rax // -32(%rbp) 被修改,该内存中的内容为 Der 虚表的偏移地址   movq  %rax, -8(%rbp)  movq  -8(%rbp), %rax  movq  (%rax), %rax  // rax = M[rax],取出虚表偏移中的地址  movq  (%rax), %rdx  // rdx = M[rax] , 取出虚表偏移的内容(也就是函数地址),算上上面这是做了两次解引用  movq  -8(%rbp), %rax  movl  $16, %esi    // sizeof(Der) = 16, 包含一个虚表指针和 int val;  movq  %rax, %rdi   // 虚表偏移中的地址  call  *%rdx      // 调用函数  leaq  -33(%rbp), %rax  movl  $1, %esi  movq  %rax, %rdi  call  _ZN1A5funnyEi  // 普通成员函数,实现简单  movl  $3, %eax  leave  .cfi_def_cfa 7, 8  ret  .cfi_endproc.LFE5:  .size  main, .-main  .weak  _ZTV3Der  .section  .data.rel.ro.local._ZTV3Der,"awG",@progbits,_ZTV3Der,comdat  .align 8  .type  _ZTV3Der, @object  .size  _ZTV3Der, 24_ZTV3Der:  .quad  0  .quad  _ZTI3Der  .quad  _ZN3Der4fuckEi // Der::fuck(int a);  .weak  _ZTV4Base  .section  .data.rel.ro._ZTV4Base,"awG",@progbits,_ZTV4Base,comdat  .align 8  .type  _ZTV4Base, @object  .size  _ZTV4Base, 24_ZTV4Base:  .quad  0  .quad  _ZTI4Base  .quad  __cxa_pure_virtual // 纯虚函数,无对应符号表  .weak  _ZTI3Der  .section  .data.rel.ro._ZTI3Der,"awG",@progbits,_ZTI3Der,comdat  .align 8  .type  _ZTI3Der, @object  .size  _ZTI3Der, 24

现在是一个纯虚函数,类中也没有虚析构函数,通过反汇编来看一些这个实现。

_ZTV3Der 和 _ZTV4Base 是两个虚表,大小为 24, 8 字节对齐,分别对应 Der 子类和 Base 父类。虚表中偏移 16 字节(偏移大小可能和实现相关)为虚函数地址,每次构造函数的被调用的时候,会将该偏移地址存储到父类指针所在内存中,所以在上代码中看到,在 Base 和 Der 类的构函数中都出现了设置偏移地址的操作,但是子类构造函数会覆盖父类的修改。这样一来,实际的函数运行地址依赖构造函数,子类对象被构造就调用子类的方法,父类构造就调用父类的方法(非纯虚函数),实现了运行时多态。

增加一个虚函数后, 后面的虚函数地址就添加到虚表之中,如下

virtual void Base::shit() {}void Der::shit() override {}_ZTV3Der:  .quad  0  .quad  _ZTI3Der  .quad  _ZN3Der4fuckEi  .quad  _ZN3Der4shitEv  .weak  _ZTV4Base  .section  .data.rel.ro._ZTV4Base,"awG",@progbits,_ZTV4Base,comdat  .align 8  .type  _ZTV4Base, @object  .size  _ZTV4Base, 32_ZTV4Base:  .quad  0  .quad  _ZTI4Base  .quad  __cxa_pure_virtual  .quad  _ZN4Base4shitEv  .weak  _ZTI3Der  .section  .data.rel.ro._ZTI3Der,"awG",@progbits,_ZTI3Der,comdat  .align 8  .type  _ZTI3Der, @object  .size  _ZTI3Der, 24

再调用另外一个虚函数就简单很多了,直接地址进行偏移(这里shit在fuck之后,所以+8)

 movq  -8(%rbp), %rax  movq  (%rax), %rax  addq  $8, %rax  movq  (%rax), %rdx  movq  -8(%rbp), %rax  movq  %rax, %rdi  call  *%rdx

简单画了一下虚函数运行的内存结构图

到此,相信大家对"C++中的虚函数如何用"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

函数 地址 偏移 内容 子类 指针 也就是 字节 实际 方法 C++ 内存 运行 多态 参数 对象 成员 这是 不同 关键 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 中关村互联网科技集团 互联网 科技公司 科技服务与互联网 上海挚品互联网科技 齐家互联网科技 润盈互联网科技 滨湖区互联网智能科技产品供应 互联网 科技馆 互联网新科技动漫ppt素材 企晟互联网信息科技有限公司 用友工业互联网科技发展 伊宁互联网科技做什么的 互联网企业科技类企业如何做账 杭州披云加互联网科技有限公司 九思连强互联网电子科技有限公司 西安左小互联网科技有限公司 数字科技和互联网金融哪个好 互联网科技创业计划的风险分析 互联网大会出现的黑科技 宁波允成互联网科技有限公司 保险科技 互联网保险 期待互联网科技 汇久创客互联网科技 伴你行互联网科技有限公司 深圳互联网科技怎么样 阿里互联网科技公司 世界互联网领先科技成果获奖 中国新注册互联网科技 陕西九四连强互联网科技有限公司 华人运通互联网科技公司
0