C++对象模型分析(四十三)

        我们学习了 C++ 这么长时间了,我们来看看 C++ 中对象的本质。它里面是用 class 定义的对象,class 是一种特殊的 struct。在内存中 class 依旧可以看做变量的集合,class 与 struct 遵循相同的内存对齐规则。class 中的成员函数与成员变量是分开存放的,及每个对象有独立的成员变量,所有对象共享类中的成员函数。那么我们如果在 class 和 struct 中同时定义相同的成员变量的话,它们所占的内存大小会一样嘛?我们来做个实验,代码如下

成都创新互联公司主要从事网站设计制作、成都做网站、网页设计、企业做网站、公司建网站等业务。立足成都服务秦都,十多年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18982081108

#include 

using namespace std;

class A
{
    int i;
    int j;
    char c;
    double d;
};

struct B
{
    int i;
    int j;
    char c;
    double d;
};

int main()
{
    cout << "sizeof(A) = " << sizeof(A) << endl;
    cout << "sizeof(B) = " << sizeof(B) << endl;
    
    return 0;
}

        我们根据之前学的知识可知,sizeof(B) 应该是等于 20 的,我们来看看 sizeof(A) 等于多少呢?

C++对象模型分析(四十三)

        我们看到 A 和 B 所占的内存大小是一样的,那便说明它们的内存分布是相同的。我们下来在 class A 中定义一个 print 函数用来打印几个成员变量的值,再定义 B 类型的指针用来强制转换指向对象 A。再用指针来改变 A 中成员变量的值,具体程序如下

#include 

using namespace std;

class A
{
    int i;
    int j;
    char c;
    double d;
public:
    void print()
    {
        cout << "i = " << i << ", "
             << "j = " << j << ", "
             << "c = " << c << ", "
             << "d = " << d << endl;
    }
};

struct B
{
    int i;
    int j;
    char c;
    double d;
};

int main()
{
    A a;
    
    cout << "sizeof(A) = " << sizeof(A) << endl;
    cout << "sizeof(a) = " << sizeof(a) << endl;
    cout << "sizeof(B) = " << sizeof(B) << endl;
    
    a.print();
    
    B* p = reinterpret_cast(&a);
    
    p->i = 1;
    p->j = 2;
    p->c = 'c';
    p->d = 3;
    
    a.print();
    
    return 0;
}

        那么我们进行强制类型转换后是否可以访问 class 的私有成员变量呢?我们来看看编译结果

C++对象模型分析(四十三)

        我们看到在进行类型转换后,我们可以直接在外部对 class 的成员变量进行直接的改变。在运行时对象会退化位结构体的形式,此时所有的成员变量在内存中一次排布,成员变量间可能存在内存空隙。我们便可以通过内存地址来直接访问成员变量,访问权限的关键字在运行时失效。

        类中的成员函数是位于代码段中,调用成员函数时对象地址作为参数隐式传递。成员函数通过对象地址访问成员变量,C++ 语法规则隐藏了对象地址的传递过程。下来我们以代码为例进行分析。

#include 

using namespace std;

class Demo
{
    int mi;
    int mj;
public:
    Demo(int i, int j)
    {
        mi = i;
        mj = j;
    }
    
    int getI()
    {
        return mi;
    }
    
    int getJ()
    {
        return mj;
    }
    
    int add(int v)
    {
        return mi + mj + v;
    }
};

int main()
{
    Demo d(1, 2);
    
    cout << "d.i = " << d.getI() << endl;
    cout << "d.j = " << d.getJ() << endl;
    cout << "d.add(3) = " << d.add(3) << endl;
    
    return 0;
}

        我们定义了一个很平常的类,在里面定义了几个返回成员变量的函数,并定义了 一个 add 函数。我们来编译看看

C++对象模型分析(四十三)

        我们看到已经正确实现。那么我们来想想,为什么我们在 getI 函数中能直接返回成员变量 mi 的值呢?是因为在 C++ 中的每个类对象都有一个隐藏的 this 指针,它时刻的指向整个对象,所以才能访问到它中的成员变量。下来我们就用 C 语言来实现上面的 C++ 程序,看看用 C 语言怎么写出面向对象的代码。

class.h 源码

#ifndef _CLASS_H_
#define _CLASS_H_

typedef void Demo;

Demo* Demo_Create(int i, int j);
int Demo_getI(Demo* pThis);
int Demo_getJ(Demo* pThis);
int Demo_add(Demo* pThis, int v);
void Demo_Free(Demo* pThis);

#endif

class.c 源码

#include "class.h"
#include 

struct ClassDemo
{
    int mi;
    int mj;
};

Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
    
    if( ret != NULL )
    {
        ret->mi = i;
        ret->mj = j;
    }
    
    return ret;
}

int Demo_getI(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mi;
}

int Demo_getJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mj;
}

int Demo_add(Demo* pThis, int v)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mi + obj->mj + v;
}

void Demo_Free(Demo* pThis)
{
    free(pThis);
}

test.c 源码

#include 
#include "class.h"

int main()
{
    Demo* d = Demo_Create(1, 2);              // Demo d(1, 2);
    
    printf("d.i = %d\n", Demo_getI(d));       // cout << "d.i = " << d.i << endl;
    printf("d.j = %d\n", Demo_getJ(d));       // cout << "d.j = " << d.j << endl;
    printf("add(3) = %d\n", Demo_add(d, 3));  // cout << "d.add(3) = " << d.add(3) << endl;
    
    Demo_Free(d);
    
    return 0;
}

        我们编译结果如下

C++对象模型分析(四十三)

        我们看到跟它后面的 C++ 代码的效果是一样的,感觉是不是很炫酷呢?下来我们来说说 C++ 中的继承对象模型。在 C++ 编译器的内部类可以理解为结构体,子类是由父类成员叠加子类新成员得到的。如下

C++对象模型分析(四十三)

        下来我们还是以代码为例来进行分析

#include 

using namespace std;

class Demo
{
protected:
    int mi;
    int mj;
public:
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << endl;
    }
};

class Derived : public Demo
{
    int mk;
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << ", "
             << "mk = " << mk << endl;
    }
};

struct Test
{
    void* p;
    int mi;
    int mj;
    int mk;
};

int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
/*        
    Derived d(1, 2, 3);
    Test* p = reinterpret_cast(&d);

    cout << "Before changing ..." << endl;
    
    d.print();
    
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    
    cout << "After changing ..." << endl;
    
    d.print();
*/    
    return 0;
}

        我们先通过打印两个类的大小来看看它们所占的内存大小

C++对象模型分析(四十三)

        分别是 8 和 12,也和我们之前所分析的是一致的。由于我们重写了 print 函数,所以我们应该将其声明为虚函数,再加上 virtual 关键字之后再来看看他们的内存大小是多少

C++对象模型分析(四十三)

        变成 12 和 16 了,加了 4 个字节的空间。我们再将注释掉的内容展开,看看结果

C++对象模型分析(四十三)

        我们通过强制类型转换来改变了他们的成员变量的值。在 struct 结构体中第一个为 void* 的指针,也就是说,在 class 类对象中还有一个指针存在。这个指针便是我们指向虚函数表的指针。那么 C++ 中多态究竟是怎么实现的呢?当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储成员函数地址的数据结构。虚函数表是由编译器自动生成与维护的,virtual 成员函数会被编译器放入虚函数表中。当存在虚函数时,每个对象都有一个指向虚函数表的指针。多态对象模型如下所示

C++对象模型分析(四十三)

        那么在编译器确认 add 函数是否为虚函数时,如果是,编译器则在对象 VPTR 所指的虚函数表中查找 add 函数的地址;如果不是,编译器则直接可以确定被调成员函数的地址。那么我们来看看具体它是怎么调用的,如下

C++对象模型分析(四十三)

        由此看来,就调用的效率来说,虚函数肯定是小于普通成员函数的。我们再次完善之前用 C 语言实现继承的代码,用 C 代码实现多态的用法。

class.h 源码

#ifndef _CLASS_H_
#define _CLASS_H_

typedef void Demo;
typedef void Derived;

Demo* Demo_Create(int i, int j);
int Demo_getI(Demo* pThis);
int Demo_getJ(Demo* pThis);
int Demo_add(Demo* pThis, int v);
void Demo_Free(Demo* pThis);

Derived* Derived_Create(int i, int j, int k);
int Derived_getK(Derived* pThis);
int Derived_add(Derived* pThis, int v);

#endif

class.c 源码

#include "class.h"
#include 

static int Demo_Virtual_Add(Demo* pThis, int v);
static int Derived_Virtual_Add(Derived* pThis, int v);

struct VTable    // 2. 定义虚函数表数据结构
{
    int (*pAdd)(void*, int);    // 3. 虚函数表里存储的东西
};

struct ClassDemo
{
    // 1. 定义虚函数表指针 ==> 虚函数表指针类型
    struct VTable* vptr;
    int mi;
    int mj;
};

struct ClassDerived
{
    struct ClassDemo d;
    int mk;
};

static struct VTable g_Demo_vtbl =
{
    Demo_Virtual_Add
};

static struct VTable g_Derived_vtbl =
{
    Derived_Virtual_Add
};

Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
    
    if( ret != NULL )
    {
        ret->vptr = &g_Demo_vtbl;    // 4. 关联对象和虚函数表
        ret->mi = i;
        ret->mj = j;
    }
    
    return ret;
}

int Demo_getI(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mi;
}

int Demo_getJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mj;
}

// 6. 定义虚函数表中指针所指向的具体函数
static int Demo_Virtual_Add(Demo* pThis, int v)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->mi + obj->mj + v;
}

// 5. 分析具体虚函数
int Demo_add(Demo* pThis, int v)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    
    return obj->vptr->pAdd(pThis, v);
}

void Demo_Free(Demo* pThis)
{
    free(pThis);
}

Derived* Derived_Create(int i, int j, int k)
{
    struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
    
    if( ret != NULL )
    {
        ret->d.vptr = &g_Derived_vtbl;
        ret->d.mi = i;
        ret->d.mj = j;
        ret->mk = k;
    }
    
    return ret;
}

int Derived_getK(Derived* pThis)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    
    return obj->mk;
}

static int Derived_Virtual_Add(Derived* pThis, int v)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    
    return obj->mk + v;
}

int Derived_add(Derived* pThis, int v)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    
    return obj->d.vptr->pAdd(pThis, v);
}

test.c 源码

#include 
#include "class.h"

void run(Demo* p, int v)
{
    int r = Demo_add(p, v);
    
    printf("r = %d\n", r);
}

int main()
{
    Demo* pb = Demo_Create(1, 2);
    Derived* pd = Derived_Create(1, 22, 333);
    
    printf("pb.add(3) = %d\n", Demo_add(pb, 3));
    printf("pd.add(3) = %d\n", Derived_add(pd, 3));
    
    run(pb, 3);
    run(pd, 3);
    
    Demo_Free(pb);
    Demo_Free(pd);
    
    return 0;
}

        我们来编译看下是不是和我们在 C++ 中实现的多态的效果是否一致呢?

C++对象模型分析(四十三)

        我们看到它的效果和 C++ 中的多态的效果是一样的,也就是说,我们用 C 语言实现了多态。屌爆了!!通过今天对 C++ 对象模型的分析,总结如下:1、C++ 中的类对象在内存布局上与结构体相同;2、成员变量和成员函数在内存中分开存放;3、访问权限关键字在运行时失效;4、调用成员函数时对象地址作为参数隐式传递;5、继承的本质就是父子间成员变量的叠加;6、C++ 中的多态是通过虚函数表实现的7、虚函数表是由编译器自动生成与维护的,虚函数的调用效率低于普通成员函数。

        欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。


网站名称:C++对象模型分析(四十三)
标题网址:http://hbruida.cn/article/picdes.html