C++对象模型(1)-- 对象模型概述

1、什么是对象模型

C++对象模型可以概括为以下2部分:

(1)语言中直接支持面向对象程序设计的部分;

(2)对于各种支持的底层实现机制。

语言中直接支持面向对象程序设计的部分,包括了构造函数、析构函数、多态、虚函数等。 对象模型的底层实现机制并未标准化,不同的编译器有一定的自由来设计对象模型的实现细节。

C++的类有2种成员变量:static, nonstatic,3种成员函数:static, nonstatic, virtual。

在C++对象模型中:

(1)nonstatic 成员变量被放置到对象内部;

(2)static成员变量、static and nonstatic成员函数均被放到对象之外。

(3)对虚函数的支持分为两步:

a)每个class的对象都会添加一个指针(vptr),指向相关的虚函数表(vtbl)。

b)每个class会为每个虚函数生成一个指针,这些指针统一放在虚函数表中(vtbl)

(4)另外,虚函数表地址的前面设置了一个指向type_info类的指针。C++提供了一个type_info类来获取对象类型信息。

假如有这么一个类:

using namespace std;
class Base {
public:
    Base(){}
    void print() { cout << "调用了普通成员函数Base::print()" << endl; }
    static void print_s() { cout << "调用了静态成员函数Base::print_s()" << endl; }
    virtual void print_v() { cout << "调用了虚函数Base::print_v()" << endl; }
    virtual ~Base() {}

public:
    char base_c[8];
    float base_f;
    int base_i;

    static int base_s; //变量声明
};
int Base::base_s = 0; //变量定义

这个类在计算机中,该怎么存储它的数据和函数呢?

我这里先给出结论,其实它的C++对象模型是这样的:

2、对象模型验证

下面,我分别用3种不同的方法来验证这个类的对象模型。我这里用VS2019来调试代码(选择X86环境)。

在main()函数里加上下面的代码:

Base base;
std::cout << sizeof(base) << std::endl;

memset(base.base_c, 0, 8);
strcpy(base.base_c, "Peter");
base.base_f = 170.5;
base.base_i = 9;
Base::base_s = 11;
    
return 0;

(1)方法1:通过快速监视查看

我们把Debug断点设在第10行:return 0的位置。运行后打开VS2019的快速"监视窗口"。

输入:&base

可以看到如下结果:

其实这就是对象base的数据布局。从上到下按顺序分别是:虚函数表指针vptr、base_c、base_f和base_i。但是没有base_s变量。

(2)方法2:用cl /d1命令查看

打开Developer Command Prompt for VS2019,在命令行中输入:cl /d1 reportSingleClassLayoutBase ch01.cpp

(3)方法3:通过读取二进制流进行确认

我们往原来的main()函数继续添加代码,改成下面这样:

int main() {

    Base base;
    std::cout << sizeof(base) << std::endl;

    memset(base.base_c, 0, 8);
    strcpy(base.base_c, "Peter");
    base.base_f = 170.5;
    base.base_i = 9;
    Base::base_s = 11;

    //下面通过代码来验证模型:
    //我们直接通过对象地址来读取对应的值

    //1.调用虚函数
    //1.1.对象的开头保存的是虚函数指针,这里用long是因为x86中虚函数指针长度是4个字节
    long* pvptr = (long*)&base;

    //1.2.把虚函数指针指向的虚函数表地址取出来
    long* vptr = (long*)(*pvptr);

    //1.3.定义函数指针
    typedef void (*Func)(void);

    //1.4.调用虚函数
    Func f1 = (Func)vptr[0];
    f1();

    //2.调用char字符串
    char* pbase_c = (char*)(((char*)&base) + 4);
    cout << pbase_c << endl;
    
    //3.调用base_f
    float* pbase_f = (float*)(((char*)&base) + 12);
    cout << *pbase_f << endl;

    //4.调用base_i
    int* pbase_i = (int*)(((char*)&base) + 16);
    cout << *pbase_i << endl;

    return 0;
}

输出结果:

我简单地讲解一下代码:

(1)long* pvptr = (long*)&base;

把存储在base首地址的虚函数指针读取出来。

(2)long* vptr = (long*)(*pvptr);

虚函数表指针指向的是虚函数表,所以*pvptr就是虚函数表地址,用long指针vptr指向虚函数表的首地址。

(3)Func f1 = (Func)vptr[0];

vptr[0]代表虚函数表里的第一个虚函数,也就是virtual void print_v()。

(4)char* pbase_c = (char*)(((char*)&base) + 4);

因为虚函数表指针占了4个字节,所以要从base首地址往下移动4个字节。这里要注意,你不能用&base + 4,必须得这样写((char*)&base) + 4。

(5)float* pbase_f = (float*)(((char*)&base) + 12);

因为base_c占了8个字节,所以要从base首地址往下移动4+8 = 12个字节才是base_f的首地址。

相关推荐
捕鲸叉23 分钟前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer27 分钟前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq29 分钟前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
青花瓷2 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
幺零九零零3 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉3 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
Dola_Pan4 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
yanlou2335 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法
小林熬夜学编程5 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
阿洵Rain5 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法