C++获取虚表地址和虚函数地址时的两个强制类型转换

本文深入探讨虚函数与虚表的工作原理,介绍如何通过强制类型转换获取虚表及虚函数地址,并提供代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天学习虚函数时看到代码中的两个强制类型转换十分不解,查了蛮多资料,终于知道了原因,因此写下自己的见解。

简单说一下虚表的概念:在一个类中如果有虚函数,那么此类的实例中就有一个虚表指针指向虚表,这个虚表是一块儿专门存放类的虚函数地址的内存。

通过sizeof运算符我们可以查看一个对象所占的空间,对于一个只有函数(无虚函数)没有属性的空类对象,它的空间长度为1,而对于有虚函数、没有属性的空类对象,无论虚函数的数量为多少,空间长度都为4,这是因为它包含一个指向虚表的指针,指针内存放的是虚表的地址。(不同环境下空间长度或许不同,但是它的空间长度一定是指针所占的空间长度)

定义一个对象后,构造器为其分配空间,而这个指向虚表的指针始终都是在第一个,也就是说这个指针的地址就是对象地址。这样我们就轻松获取到了这个指向虚表的指针的地址。

我们可以把虚表想象为一个存放许多地址的指针数组,它的内容就是虚函数的地址。

因此,我们就找到了获取虚表地址和虚函数地址的方法。

实例化一个对象,指向虚表的指针的地址就是对象的首地址,指针的内容就是对象的前四个字节,也就是虚表的地址,剩下的就当作一个指针数组处理就可以了。

#include<iostream>
using namespace std;
class base
{
public:
        int a;
public:
        virtual void showa()
        {
                cout<<"base::a"<<endl;
        }
        virtual void showb()
        {
                cout<<"base::b"<<endl;
        }
        virtual void showc()
        {
                cout<<"base::c"<<endl;
        }
};
int main()
{
        base b;
        cout<<sizeof(b)<<endl;
        cout<<&b<<endl;    //对象首地址
        cout<<(int*)&b<<endl;    //指针首地址,(int*)强制类型转换是为了取四个字节的内容
        cout<<*(int*)&b<<endl;    //指针的内容,即虚表的地址
        cout<<*((int*)*(int*)&b)<<endl;    //虚表第一个元素即虚函数showa()的地址
        cout<<*((int*)(*(int*)&f)+1)<<endl;    //第二个元素的地址
        /*
        *这里可能有人奇怪虚表的地址不就是第一个元素的地址,为什么这里要加(int*)?
        *代码的后面会有解释
        */
        typedef void(*Fun)(void);    //重命名函数指针类型
        Fun pfun;
        pfun=(Fun)*(int*)*(int*)&b;    //强制类型转换为该函数指针类型
        b.showa();
        pfun();
        return 0;
}

加(int*)的原因:

其实它(int*)的作用是一样的,都是为了取四个字节的内容,至于为什么要这样做就涉及到了指针的基础知识。

我们都知道定义指针变量时一定要声明类型,不同于其他基础类型,定义int变量就是4个字节,定义char变量是1个字节,定义short变量就是2个字节,我们都知道无论是什么类型的指针都占4个字节,那么声明类型到底有什么用呢?

它就是为了限定指针在内存上取变量的长度,这样说可能不太清楚,我们来看代码:

#include<stdio.h>
int main()
{
        int a=134480385;
        int* pa=&a;
        char* pb=&a;
        short* pc=&a;
        printf("*pb=%d  *pc=%d  *pa=%d\n",*pb,*pc,*pa);
}

a的二进制表示为:0000 1000    0000 0100    0000 0010  0000 0001

这样大家看的应该就比较清楚了,char类型的指针pb只会取一个字节,因此取出的数为0000 0001,即1

short类型的指针pc会取两个字节,因此取出的数为0000 0010 0000 0001,即513

同理,int类型指针取四个字节,会全部取出。

好了,现在回到虚表中的(int*)中,现在大家是不是能够理解为什么了呢。如果不加(int*)编译器就不知道应该取的长度

在取虚表地址的代码中,我们可以看到这个虚表指针是没有名字的,因此就无法像我们平常使用指针那样直接调用变量来引用内容,只能通过*(int*)&b的方式获取指针的内容,*(int*)&b就是获取对象b空间上的前四个字节的内容。

同理,虚表虽然像一个指针数组,但是我们不知道它的名字,也就无法像原来使用数组那样直接使用数组名和下标的方式使用,只能通过地址来调用内容。*(int*)*(int*)&b,就是在虚表的空间上取四个字节,它就是第一个虚函数的地址。(int*)(*(int*)&f)+1是第二个元素的地址,加*取它的内容,即第二个虚函数的地址。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值