C++冷知识(四)——类对象及虚函数表的内存空间排布

讲正题之前先复习一下指针的占用空间大小。指针的占用空间与编译器位数有关,当编译器为64位时,指针占用空间为8字节;当编译器为32位时,指针占用空间为4字节。

其实指针也就是地址,保存一个指针的占用空间与一个地址的大小是一样的。

1、普通类(即没有虚基类和虚函数)的对象中,其内存中保存的数据即为基类的非静态数据成员与当前类的非静态数据成员 及 内存对齐所占用的额外空间。

2、重点来了,对于存在虚基类和虚函数的类,其对象会自动多一个非静态数据成员(该数据成员是一个指针,指针内容保存虚函数表的地址),而且该非静态数据成员位于最前面,即所谓的“第一个成员”。效果如下图:

原声明如下:

struct A
{
    int value;
    virtual void foo() { }
};

这个A的非静态数据成员将会是如下图所示:

struct A
{
    void *virtual_table;
    int value;
};

假设一个地址是用8字节表示的,则从该对象的起始地址开始,到接下来的7个字节,这8个字节保存的是虚函数表的地址,假设一个对象名称为object,则可以通过 * reinterpret_cast<unsigned long long *>(&a)取得该8字节的内容,所得内容即为虚函数表的地址。那么对象的内存空间如下图:

对于虚函数表的内存空间,则按照虚函数的声明顺序向下排列,如下图代码:

class A
{
    virtual void func1() { }
    virtual void func2() { }
    virtual void func3() { }
    virtual ~A() { }
};

其虚函数表空间如下图:

下面代码将演示如何获取类的虚函数地址方法:

#include <iostream>

//用于获取能保存一个指针大小的基本数据类型
template<std::size_t>
struct AddressSize
{
    typedef int type;// 指针占用4字节
};
// 模板特化
template<>
struct AddressSize<8>
{
    typedef unsigned long long type;// 指针占用8字节
};

class A
{
public:
    virtual void func1()
    {
        std::cout << "this is func1" << std::endl;
    }
    virtual void func2(double)
    {
        std::cout << "this is func2" << std::endl;
    }
    virtual void func3(int)
    {
        std::cout << "this is func3" << std::endl;
    }
    virtual ~A()
    {
        std::cout << "this is ~A()" << std::endl;
    }
};

int main()
{
    A a;
    typedef AddressSize<sizeof(void *)>::type pointer_size_type;//获取能代表一个指针大小的基本数据类型
    typedef void(*func1)();
    typedef void(*func2)(double);
    typedef void(*func3)(int);
    //获取对象的起始地址
    pointer_size_type *address = reinterpret_cast<pointer_size_type *>(&a);
    //得到虚函数表地址的数值
    pointer_size_type virtual_func_table_address = *address;
    //获得虚函数表地址数值转换为指针
    pointer_size_type *virtual_func_list_address = reinterpret_cast<pointer_size_type *>(virtual_func_table_address);
    //获取func1函数的地址,因为func1函数是第一个声明的函数,所以加0
    pointer_size_type func1_address = *(virtual_func_list_address + 0);
    func1 f1 = reinterpret_cast<func1>(func1_address);
    f1();
    //获取func2函数的地址,因为func2函数是第二个声明的函数,所以加1
    pointer_size_type func2_address = *(virtual_func_list_address + 1);
    func2 f2 = reinterpret_cast<func2>(func2_address);
    f2(0.0);
    //获取func3函数的地址,因为func3函数是第一个声明的函数,所以加2
    pointer_size_type func3_address = *(virtual_func_list_address + 2);
    func3 f3 = reinterpret_cast<func3>(func3_address);
    f3(1);
    std::cout << "finish" << std::endl;
    return 0;
}

运行结果如图:

 

本次内容结束,如有问题,欢迎指出!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值