百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

C++对象模型(4)-vtable和vptr的类型问题

liebian365 2025-03-18 23:45 2 浏览 0 评论

我们知道vtable是一个函数指针数组,存放的是虚函数地址,但是从语言层面讲,只有相同类型的对象才能放入同一个数组中,也就是说,你无法声明一个可以同时包含多种不同类型对象的数组,对函数指针数组来说也是如此。

其实C++编译器定义了一个通用的函数指针类型:

typedef void(*Pvfn)(void); // 通用的虚函数指针类型

vtable类型如下(伪代码):

typedef struct {
    Typeinfo *_pinfo;
    Pvfn _array[];  // 虚函数个数由初始化语句确定 
} Vtable;

在每一个继承分支中的第一个多态类中插入vptr,而在每一个多态类中都插入vtable的声明

举例如下(伪代码):

class Shape {
    Pvfn *_vptr;
    static Vtable _vtable;
 public:
    Shape(): m_color(0){}
    virtual ~Shape(){}
    float GetColor() const {return m_color;}
    void SetColor(float color) {m_color = color;}
    virtual void Draw() = 0;
private:
    float m_color;
};

class Rectangle : public Shape {
    static Vtable _vtable; // 在实现文件中初始化
public:
    Rectangle(): m_length(1), m_width(1) {}
    ~Rectangle() {}
    virtual void Draw() {}
......
}

编译器在编译完一个class后,把class中所有虚函数的指针(实际就是被转换为全局函数后的地址,函数地址是编译时常量)都强制转换为Pvfn类型,并用它们初始化_vtable的_array数组;而_vptr则被初始化为指向_vtable对象,具体指向哪里取决于编译器实现,比如g++编译器会指向vtable中第一个虚函数指针,可以参见C++对象模型(3)-使用gdb查看vtable

在遇到通过指针或者引用调用虚函数的语句时,首先根据指针或者引用的静态类型来判断所调虚函数是否属于该class或者它的某个public基类,然后进行静态类型检查,例如:

Shape *pShape = new Rectangle;
pShape->Draw(); // 根据Shape::Draw()执行类型检查

检查通过后,就剩下最后一步工作了:改写虚函数调用语句,改写类似下面的样子:

(*(pShape->_vptr[2]))(pShape); //pShape->Draw();

具体索引值取决于vtable的构造。

但是,语句(pShape->_vptr[n])从vtable中取出来的函数指针类型应该是Pvfn,与实际调用的虚函数的类型一般是不匹配的(编译器知道我们定义的每一个虚函数的类型),所以还应该有一个反向类型强制转换的过程。最后的结果应该类似下面这样(仅作示意):

typedef void(*Pvfn_Draw)(void);
(*(Pvfn_Draw)(pShape->_vptr[2]))(pShape);//pShape->Draw();           

在整个过程中并没有派生类改写的虚函数Rectangle::Draw()参与其中。那怎么会调用到这个改写的虚函数呢?奥妙就在vtable的构造及pShape当前实际指向的对象。

虽然pShape的静态类型是Shape*,但是在运行时它却指向一个Rectangle对象,而该对象的vptr成员指向Rectangle::_vtable,而不是Shape::_vtable;这个vtable中存放的也都是Rectangle改写过的虚函数或者新加的虚函数的地址,而不是Shape的虚函数地址,除非有的虚函数Rectangle没有改写。

相关推荐

几句代码实现搜索内存、解密数据库

本文只分享编程技术,不涉及具体软件。涉及具体软件的文章或工具出现很多年了,到处都是。头条上也有很多,这里我们不讨论。有用户问我:登录后才能解密,输入密码后才能备份出数据库,这些本来就是我自己可以查看的...

JDK 11 新特性总结(jdk最新特性)

一、语言特性增强局部变量类型推断升级支持在Lambda表达式参数中使用var关键字,编译器自动推断类型,简化代码编写并保持类型安全。...

和爷爷一起学Arduino:四位七段数码显示(学习面向对象编程)

2018年,我们买了个七段四位数码显示LED组件,如下图。经试验,它是与TM1637兼容的。右侧的引脚从上到下依次是,G(GND)、D(Data,数据)、C(Clock,时钟)、V(Vcc)。有两种,...

Linux 技巧:重定向 stderr 和 stdout 输出到 gdb 窗口

简介本文介绍了一个实用gdb调试技巧。它结合实际例子,一步一步示意如何重定向stderr和stdout到gdb窗口,使得查看应用程序的输出信息更为方便,从而提高调试者的工作效率。问题为...

CLion 1.0发布,C/C++跨平台集成开发环境

日前,知名开发者工具厂商JetBrains(捷克的一家软件开发公司)正式发布了一款跨平台的C/C++集成开发环境CLion1.0。这款强大的IDE旨在让你基于Linux、OSX、Windows系...

「运维经」第25章——gdb最实用的那几条命令

实用调试操作1setscheduler-lockingoff|on...

XV6操作系统入门系列-02-详解启动过程

第零步-心理上的准备工作任何事物都有其关键的窍门,当我们抓住了关键,事情会变得简单起来;当我们没有抓住要领,事情就会变得异常困难。...

GDB德国格德宝|OEM|奔驰车厂认证(德宝格机械)

MBMercedes油规格MB规范的名称源自奔驰蓝皮书计划,除以编号的段落和页面。经销商使用它来识别制造商认证的产品及其在发动机上的正确应用。...

o1已不是聊天模型了!SpaceX前工程师公开全新使用秘籍

梦晨发自凹非寺量子位|公众号QbitAI苹果&SpaceX前工程师分享o1使用心得,奥特曼、Brockman都转发了。...

ARM平台如何玩转GDB远程调试?(arm gdbserver)

前言关于GDB工具GDB工具是GNU项目调试器,基于命令行使用。和其他的调试器一样,可使用GDB工具单步运行程序、单步执行、跳入/跳出函数、设置断点、查看变量等等,它是UNIX/LINUX操作系统下...

ChatGPT击败50名人类医生!疾病诊断准确率达90%,OpenAI总裁:人机合作还得加强

...

GDB高级技巧:边Debug边修复BUG,无需修改代码,无需重新编译

友情提醒:本文介绍的调试技巧非常实用,但为了讲解清楚,篇幅较长,请耐心看完,我保证你定会有收获!引言程序调试时,你是否遇到过下面几种情况:1、经过定位,终于找到了程序中的一个BUG,满心欢喜地以为找到...

实现多态必须满足什么条件(实现多态的两种方式)

虚函数机制virtualmechanism先看代码:classA{public:virtualvoidprint(){cout<<"A.."<<endl;}...

gdb查看寄存器及内存数据与函数调用栈分析

在分析kdump生成的vmcore文件时,有时会需要分析函数调用栈及函数参数与局部变量的情况,这里以使用gdb为例调试分析一下函数调用的栈帧创建与销毁。操作系统:centos73.10.0-862...

C++语言求数组元素最大值及其下标例程(指针学习与运用)

C++语言编写求数组元素最大值及其下标例程(指针学习与运用)文章logo#include"stdafx.h"...

取消回复欢迎 发表评论: