拆一下C++ 的对象模型 (现在来看主要拆的是G++的)

11 篇文章 0 订阅

1. 关于类中的成员数据和成员函数:

#include <iostream>

using   namespace       std;

class   ClassA {
public:
        ClassA () {
        }
        ~ClassA () {
        }
        void publicFunc () {
        }
        virtual void vPublicFunc () {
        }
        int publicV1;
        int publicV2;
        static int staticClassA;
private:
        void privateFunc () {
        }
        virtual void vPrivateFunc () {
        }
        int privateV1;
        int privateV2;
};

class   ClassB : public ClassA {
public:
        ClassB () {
        }
        void publicFunc () {
        }
        int publicV;
        static int staticClassB;
private:
        void privateFunc () {
        }
        int privateV;
};

int
main (int argc, char *argv[]) {
        ClassA cA;
        ClassB cB;

        cout << "sizeof ClassA is : " << sizeof (ClassA) << endl;
        cout << "sizeof ClassB is : " << sizeof (ClassB) << endl;

        return 0;
}


结果(环境是MinGW Shell):

成员函数不是存在对象中的,sizeof ClassA的结果来看有点奇怪,应该是4个变量和1个静态变量——但是这样的话如何静态呢。

详细看看类的内部有什么:

用gdb 打印一下就能看到为什么ClassA 和ClassB 的sizeof 会得到这样的值了,首先都包涵了一个_vptr.ClassName 的指针,这货指向了一个虚表。

然后是5个变量,其中静态变量不在类中。

同样,很容易看出来,ClassB 中继承自ClassA,其中先存放基类的数据成员,然后自己的数据成员再按照一定顺序存放。

关于顺序,打印下cA 中变量的地址如下:

和p cA 指令得到的一个样,cA 的数据结构是:

privateV2high addr
privateV1 
publicV2 
publicV1 
vptrlow addr

其中成员函数在类外,一般编译器这样实现成员函数:

如果有类ClassA,其中有公有函数pFunc (void)。

编译时该函数会在类外定义,原本的调用语句

cA.pFunc ();

会变为_cA_pFunc (&cA) // 这里不同的编译器实现不一样,但是一定会传入一个this 指针

虽然成员函数都在类外,然而普通函数和虚函数还不一样(记得虚表指针么)。

至于虚函数,请看2。

2. 关于类开始的虚表指针和类的私有变量:

#include <iostream>

using   namespace       std;

class   ClassA {
public:
        ClassA (int pV1, int pV2) : pV1 (pV1), pV2 (pV2) {
        }
        virtual void vFunc () {
                cout << "虚函数vFunc 被执行" << endl;
        }
        virtual void vFunc2 () {
                cout << "虚函数vFunc2 被执行" << endl;
        }
        void printV () {
                cout << "pV1 = " << pV1 << " | pV2 = " << pV2 << endl;
        }
private:
        int pV1, pV2;
};

int
main (int argc, char *argv[]) {
        ClassA cA (1, 2);

        // 看看私有变量
        cA.printV ();
        // C++ 类的私有类型果真是”私有“的么?
        ((int *)&cA)[1] = 2, ((int *)&cA)[2] = 1;
        // 再看看私有变量
        cA.printV ();

        // 传言第一个4byte 是一个指向虚表的指针,虚表顾名思义就是虚拟函数表
        // 把指向的虚表每4byte 作为函数调用,看看会得到什么
        for (int i = 0; ((int *)*((int *)&cA))[i]; ++i) {
                (*((void (*) ())((int *)*((int *)&cA))[i])) ();
        }

        getchar ();
        return 0;
}


结果(环境是wxDev C++ 7.4.1.13):

注意顺序哦亲。

现在我们可以了解虚表是什么东东了,大约是下面的样子

                 类ClassA                             虚表                        虚函数

(high addr)      pV2                                  NULL ①
                 pV1                                  *pFunc2  -----------------> vFunc2
(low addr)       *vptr  --------------------------->  *pFunc1  -----------------> vFunc:

在G++ 中,这个值为NULL 代表了只有一个虚表,为1代表还有下一个虚表

既然我们了解虚表了,我们能对虚表指针做些手脚么?

#include <iostream>

using   namespace       std;

class   ClassA {
public:
        ClassA () {
        }
        virtual void vFunc () {
                cout << "我是ClassA 中的虚函数" << endl;
        }
};

class   ClassB  {
public:
        ClassB () {
        }
        virtual void vFunc () {
                cout << "我是ClassB 中的虚函数" << endl;
        }
} ;

int
main (int argc, char *argv[]) {
        ClassA *cA = new ClassA;
        ClassB *cB = new ClassB;

        cA->vFunc ();
        *((int *)&cA) = *((int *)&cB);
        cA->vFunc ();   // 现在调用的还是原先的虚函数么?

        delete cA;
        delete cB;

        getchar ();
        return 0;
}


结果(环境wxDev C++ 7.4.1.13):

虚表指针被替换了,指向了ClassB 的虚表。

上面只是测试了public 的虚函数,那么private 的虚函数呢?

我们把2 中第一段代码稍稍修改:

#include <iostream>

using   namespace       std;

class   ClassA {
public:
        ClassA (int pV1, int pV2) : pV1 (pV1), pV2 (pV2) {
        }
private:
        virtual void vFunc () {
                cout << "虚函数vFunc 被执行" << endl;
        }
        virtual void vFunc2 () {
                cout << "虚函数vFunc2 被执行" << endl;
        }
        void printV () {
                cout << "pV1 = " << pV1 << " | pV2 = " << pV2 << endl;
        }
private:
        int pV1, pV2;
};

int
main (int argc, char *argv[]) {
        ClassA cA (1, 2);

        for (int i = 0; ((int *)*((int *)&cA))[i]; ++i) {
                (*((void (*) ())((int *)*((int *)&cA))[i])) ();
        }

        getchar ();
        return 0;
}

结果(环境wxDev C++ 7.4.1.13):

既然私有虚函数也在虚表之内,那么虚析构函数呢?

我们修改下上面的代码:


#include <iostream>

using   namespace       std;

class   ClassA {
public:
        ClassA () {
        }
        virtual ~ClassA () {
                cout << "虚析构函数被执行" << endl;
        }
        virtual void vFunc () {
                cout << "虚函数vFunc 被执行" << endl;
        }
};

int
main (int argc, char *argv[]) {
        ClassA cA ;

        // 把指向的虚表每4byte 作为函数调用,看看会得到什么
        for (int i = 0; ((int *)*((int *)&cA))[i]; ++i) {
                (*((void (*) ())((int *)*((int *)&cA))[i])) ();
        }

        getchar ();
        return 0;
}



结果(环境wxDev C++ 7.4.1.13):

至于为什么虚析构函数被调用了2次,我会试图弄清楚。②

虚函数无论访问权限如何,都存在于虚表之中,所以你可以通过虚表来调用私有虚函数。

C++ 的类并不能保证数据的封装性,就像上面调用私有虚函数或者直接修改私有变量一样。

我开了个帖子,得到一个比较靠谱的回答如下:

第一个“虚析构函数”是vtbl第一个项目的函数调用产生的,这就是多出来的那一个。据《深度探索C++对象模型》一书介绍,vtbl第一个项目通常是一个type_info对象的指针,但g++的类对象内存布局是否真这样,偶没具体研究过g++的内存布局,也没找到其它介绍g++内存布局的资料,无从考究这个多出来的“虚析构函数”是如何产生的。

3. 如果虚函数被继承……

2中我们了解了虚表,但是如果再加上继承,虚表会怎么变化呢?

单继承:

#include <iostream>

using   namespace       std;

// 宏的参数*必须*是对象的指针
#define CALL_V_FUNCS(P_OBJECT)\
        cout << "下面是"<< #P_OBJECT\
                << " 对象中虚表中函数的调用" << endl;\
        for (int i = 0; ((int *)*((int *)(P_OBJECT)))[i]; ++i) {\
                (*((void (*) ())((int *)*((int *)(P_OBJECT)))[i])) ();\
        }

class   ClassA {
public:
        ClassA () {
        }
        virtual void vFunc () {
                cout << "我是ClassA 中的虚函数vFunc" << endl;
        }
        virtual void vFunc1 () {
                cout << "我是ClassA 中的虚函数vFunc1" << endl;
        }
};

class   ClassB : public ClassA {
public:
        ClassB () {
        }
        virtual void vFunc2 () {
                cout << "我是ClassB 中的虚函数vFunc2" << endl;
        }
        virtual void vFunc3 () {
                cout << "我是ClassB 中的虚函数vFunc3" << endl;
        }
} ;

int
main (int argc, char *argv[]) {
        ClassA *cA = new ClassA;
        ClassB *cB = new ClassB;

        CALL_V_FUNCS (cA);
        CALL_V_FUNCS (cB);

        delete cA;
        delete cB;

        getchar ();
        return 0;
}


结果(环境wxDev C++ 7.4.1.13)

可以看出,ClassB 中的虚表还是一个(我的上面代码只能读单虚表的情况),ClassA 的虚函数指针在虚表前2个位置,而后是ClassB 自身的2个虚函数指针。

如果再加上虚函数的覆盖呢?‘

这段代码几乎和上面的一段一模一样,只不过ClassB 中的虚函数覆盖了ClassA 中的一个:

#include <iostream>

using   namespace       std;

// 宏的参数*必须*是对象的指针
#define CALL_V_FUNCS(P_OBJECT)\
        cout << "下面是"<< #P_OBJECT\
                << " 对象中虚表中函数的调用" << endl;\
        for (int i = 0; ((int *)*((int *)(P_OBJECT)))[i]; ++i) {\
                (*((void (*) ())((int *)*((int *)(P_OBJECT)))[i])) ();\
        }

class   ClassA {
public:
        ClassA () {
        }
        virtual void vFunc () {
                cout << "我是ClassA 中的虚函数vFunc" << endl;
        }
        virtual void vFunc1 () {
                cout << "我是ClassA 中的虚函数vFunc1" << endl;
        }
};

class   ClassB : public ClassA {
public:
        ClassB () {
        }
        virtual void vFunc () {
                cout << "我是ClassB 中的虚函数vFunc" << endl;
        }
        virtual void vFunc3 () {
                cout << "我是ClassB 中的虚函数vFunc3" << endl;
        }
} ;

int
main (int argc, char *argv[]) {
        ClassA *cA = new ClassA;
        ClassB *cB = new ClassB;

        CALL_V_FUNCS (cA);
        CALL_V_FUNCS (cB);

        delete cA;
        delete cB;

        getchar ();
        return 0;
}


结果(环境wxDev C++ 7.4.1.13)

可以看出,ClassB 的虚函数vFunc 的指针覆盖了ClassA 中vFunc 的指针在虚表中的位置,从而实现了代码的重用。

如果是多继承呢:

关于虚表,其实有些地方都是要看编译器具体实现的。多继承的虚表太依赖于编译器实现(传言VS和GCC就不一样),因此无法写出统一的代码。

然而多继承能够确定的就是:

A. 子类会继承父类的虚表,假设有3个有虚表的父类,子类就会有3个虚表。

B. 子类的虚函数被放在第一个父类的虚表中,规则和上面演示的单继承一致。

C. 如果多继承的过程中发生了虚函数的覆盖,那么规则也和上文单继承虚函数的覆盖规则的一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《深度探索C++对象模型》是由侯捷所著的一本经典的C++图书,该书于2012年由机械工业出版社出版。本书的主要内容涵盖了C++对象模型的深入解析和探讨。 在书中,作者详细讲解了C++中的对象模型和相关的概念,如类、对象、继承、多态等。作者首先介绍了C++对象模型的基本概念和特点,包括对象的内存布局、虚函数表和虚函数指针等。然后,作者深入探讨了C++中的继承机制和多态性,包括单继承、多继承、虚继承等。作者还详细介绍了虚函数的实现原理和使用方法。 在书中,作者对C++对象模型的实现细节进行了深入的剖析,包括成员变量和成员函数的内存布局、函数指针和成员函数指针的用法等。同时,作者还讨论了C++中的一些高级特性,如模板、内存管理和异常处理等。通过对C++对象模型的深度探索,读者可以更好地理解C++的内部机制和原理,提高程序设计和开发能力。 《深度探索C++对象模型》适合具有一定的C++编程基础的读者阅读,尤其是对C++对象模型感兴趣的读者。通过阅读本书,读者可以进一步了解C++的底层实现和运行机制,从而提高自己的编程能力和代码质量。此外,本书还提供了大量的示例代码和实践案例,可以帮助读者更好地理解和应用所学知识。 总之,《深度探索C++对象模型》是一本深入探讨C++对象模型的经典著作,通过对C++的底层实现和内部机制的剖析,帮助读者深入理解C++编程语言,并提高自己的软件开发能力。 ### 回答2: 《深度探索C++对象模型》是由Stanley B. Lippman于1994年所著的一本经典畅销的C++书籍,该书详细介绍了C++对象模型的内部实现细节。 C++对象模型是指C++编译器在处理对象、继承、多态等面向对象特性时所采用的具体实现方式。这本书通过对对象模型的剖析,帮助读者深入理解C++的内部工作原理,从而写出更高效、更可靠的C++代码。 在《深度探索C++对象模型》中,作者首先介绍了对象、虚函数、继承等C++核心概念,然后详细讲解了C++对象模型的构建过程,包括对象布局、成员函数指针、虚函数表等。作者逐步深入地剖析了C++对象模型在内存中的表示方式,解释了为什么C++可以支持如此强大的面向对象特性。 此外,本书还探讨了一些高级主题,如多重继承、虚拟继承、构造函数和析构函数的执行顺序等。对于想要深入学习C++的读者来说,这本书提供了一些宝贵的技术手册和实用的经验。 尽管《深度探索C++对象模型》的出版时间是1994年,但它仍然被广泛认可为学习C++对象模型的经典之作。在2012年时,由于C++的发展和演进,也许一些内容已经有些过时,但很多基本概念和原理仍然适用。 总而言之,《深度探索C++对象模型》是一本值得阅读的C++经典著作,通过深度探索C++对象模型,读者可以更加深入地了解C++的内部工作原理和实现方式,提升自己的开发技能。 ### 回答3: 《深度探索C++对象模型》是一本于2012年出版的书籍。该书的作者Andrews和Sorkin以全面的角度深入探讨了C++对象模型。该书重点介绍了C++中的对象表示、虚函数、继承、多重继承、构造函数、析构函数等内容,以及与之相关的语法、原理和底层实现。 这本书为读者揭示了C++对象模型的奥秘,让人更加深入地理解C++语言中的类和对象。作者通过分析对象布局、虚函数表、虚函数调用、多继承中的数据布局和函数调用等等,解释了C++对象模型的实现机制。 在读者了解C++对象模型的基础上,该书还介绍了如何有效地利用对象模型来提高程序的性能。作者讨论了虚函数的成本以及如何减少虚函数调用的开销,提供了一些优化技巧。此外,书中还对C++的构造函数和析构函数进行了深入的讨论,详细解释了构造函数和析构函数的执行机制和注意事项。 总的来说,《深度探索C++对象模型》是一本深入剖析C++对象模型的重要参考书籍。通过阅读该书,读者可以更加全面地了解C++的类和对象的实现原理,对于理解C++语言的底层机制和优化程序性能具有积极的作用。无论是对于初学者还是有一定C++基础的开发人员来说,该书都是一本值得阅读的重要参考书。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值