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的首地址。

相关推荐
小俊俊的博客1 小时前
海康RGBD相机使用C++和Opencv采集图像记录
c++·opencv·海康·rgbd相机
_WndProc1 小时前
C++ 日志输出
开发语言·c++·算法
薄荷故人_1 小时前
从零开始的C++之旅——红黑树及其实现
数据结构·c++
m0_748240021 小时前
Chromium 中chrome.webRequest扩展接口定义c++
网络·c++·chrome
qq_433554541 小时前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
努力学习编程的伍大侠1 小时前
基础排序算法
数据结构·c++·算法
yuyanjingtao2 小时前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
闻缺陷则喜何志丹2 小时前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
charlie1145141912 小时前
C++ STL CookBook
开发语言·c++·stl·c++20
小林熬夜学编程3 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http