zoukankan      html  css  js  c++  java
  • 虚函数地址C++虚函数的一点分析与思考

    最近研究虚函数地址,稍微总结一下,以后继续补充:

        简介:

        以下是自己看过的书籍以及自己思考的流程和总结,主要是对C++虚函数分析了,分析并不算足够深入,但相信对懂得c++的虚函数会有些帮助。现在仅仅写到了单继承下的一些皮毛,前面还要继续挖掘一下,希望自己能以淡定一点的心做好一块,不负自己。

        以下内容适合了解一些C++虚函数以及对指针操纵相对来说有点基础的友人,因为里面为了验证自己的思考停止了很多指针的强转,上面的测试需要有实际的代码操纵,不希望大家仅仅看结论就算完了,那很没有意思。我在本地建了一个vs工程,先从最简略的测试做起,然后一点点的参加代码.一点点的深入,代码写的比拟乱,仅仅是为了自己的测试,望友人包容一下了。

        代码如下:

        Shape.h

        log.h

        typedef.h

        main.cpp

        Shape.h:

        #ifndef __SHAPE__HEAD_H

        #define __SHAPE__HEAD_H

        #include "typedef.h"

        #include "log.h"

        class CShape

        {

        public:

        CShape(){

        TRACE_FUCTION_AND_LINE("");

        m_color = 15;

        }

        ~CShape(){

        TRACE_FUCTION_AND_LINE("");

        }

        void SetColor(int colore){

        TRACE_FUCTION_AND_LINE("");

        m_color = colore;

        }

        protected:

        private:

        int m_color;

        };

        class CRect : public CShape

        {

        public:

        CRect(){TRACE_FUCTION_AND_LINE(""); m_width = 0; m_height = 255;}

        ~CRect(){TRACE_FUCTION_AND_LINE("");}

        void PrintMemory(){

        TRACE_FUCTION_AND_LINE("this: %p", this);

        int *p = (int*)this;

        TRACE_FUCTION_AND_LINE("4: %d", *p);

        TRACE_FUCTION_AND_LINE("4: %d", *(p+1));

        TRACE_FUCTION_AND_LINE("4: %d", *(p+2));

        }

        protected:

        private:

        int m_width;

        int m_height;

        };

        #endif

        log.h:

        #define TRACE_FUCTION_AND_LINE(fmt, ...) printf("[%30s:%4d] "fmt"\n",__FUNCTION__, __LINE__, ##__VA_ARGS__)

        typedef.h:

        仅仅是一些跨平台的宏定义,就不列出来了,针对我们的问题不起主要作用。

        main.cpp:

        #include <iostream>

        using namespace  std;

        #include "Shape.h"

        int main()

        {

        CRect rect1;

        TRACE_FUCTION_AND_LINE("sizeof(CShape):%d", sizeof(CShape));

        TRACE_FUCTION_AND_LINE("sizeof(CRect):%d, %p", sizeof(CRect), &rect1);

        rect1.PrintMemory();

        rect1.SetColor(10);

        rect1.PrintMemory();

        return 0;

        }

        问题1:

        派生类和基类的内存如何排布?

        通过main.cpp中rect1的打印内存的操纵我们可以看出,派生类占用12字节内存,分离是基类的m_color,以及自己的两个int成员。

        基类据有4个字节的内存,SetColor函数本身不占用任何内存。

        真理:对象所占用内存包括3个部分,非静态数据成员(自身的和父类的),vptr(前面分析),字节对齐。

        因此不要武断的说,c++占用内存比C多,其实就一个vptr的问题,字节对齐在结构体中同样会涌现,字节对齐对上层来说是透明的,因此不必太过于例会。

        派生类如何调用基类的非虚public函数,例如本例的SetColor?

        1,对于SetColor方式,编译器会将其编译成SetColor(int colore, CShape* pShape); rect停止调用的时候采取的纯C的方式,也就没有任何多余的开销。

        rect1.SetColor(color)将会被展开为 SetColor(color, &rect1); 于是rect1的地址被传入到pShape中,继而调用pShape->m_color给m_color赋值。

        对于编译器来说,它只看到传过去的参数地址为&rect1, 它并不晓得它的实际类型是什么,对于任何类型都将会被SetColor强转为CShape的类型,于是这就引出一个问题,编译器怎么晓得实际的rect1的m_color地址偏移是多少呢?实际上它压根就不晓得它是CRect类型.在SetColor这个函数指令运行的时候,它仅仅是根据传入pShape的地址,以CShape的方式偏移特定的地址(对于本例子偏移为0),然后赋值。可以判断对于子类CRect来说, 也是以偏移为0的地址停止赋值的;换句话说,子类对象的内存有一块内存是父类的,而且父类的内存必须在内存块的前半部分,要不对rect1的地址偏移为0的地址赋值时,就有可能赋值到另一个数据成员上,而不是m_color。

        2,如何验证此设法?

        1)根据上面例子的内存打印可以看出,rect1的m_color的内存确切在地址的前半部分。

        2)可以给SetColor传递一个假的CShape类型,不雅测其是否确切是对假的对象的前四个字节赋值?

        测试代码如下:

        struct FakeCRect{

        int fake1;

        int fake2;

        int fake3;

        int fake4;

        }fakerect = {1,1,1,1};

        TRACE_FUCTION_AND_LINE("fakerect:%d, %d, %d, %d", fakerect.fake1, fakerect.fake2, fakerect.fake3, fakerect.fake4);

        CRect* pfakerect = (CRect*)&fakerect;

        pfakerect->SetColor(20);

        TRACE_FUCTION_AND_LINE("fakerect:%d, %d, %d, %d", fakerect.fake1, fakerect.fake2, fakerect.fake3, fakerect.fake4);

        打印结果如下:

        main:  23] fakerect:1, 1, 1, 1

        main:  26] fakerect:20, 1, 1, 1

        可以看到确切是第一个字节变了,传入一个假的CRect类型,它仍然是对其第一个int变量处理,你甚至可以传递一个char型的数组来测试都行。

        3,拓展延伸,这种情况的破例。

        1)上面的例子没有斟酌带有虚函数的情况,现在给父类和子类分离参加一个虚函数,display方法。子类继承父类的虚函数,并重写此方法。

        virtual void display(){

        TRACE_FUCTION_AND_LINE("");//打印以后函数的名字和行号,便于判断是调用哪一个类的display方法。

        }

        这个时候可以看到,父类和子类的对象内存大小转变了,分离增加了四个字节,分离是8, 16,而且根据子类的PrintMemory可以清晰的看到添加的内存是占用的对象最前面四个字节,其他不变。虚函数机制使得使用基类的指针指向不同的对象来实现多态成为现实。pShape->display();

        通过pShape指向不同的对象, 将会调用不同方法,可以再创立一个CCircle类继承于CShape类来不雅测这种结果。

        CShape* pShape = new CCircle();

        pShape->display();//调用CCircle的display方法

        pShape = new CRect;

        pShape->display();//调用CShape的display方法

        如果你还不是很清晰虚函数的用法或者说你不盘算深入探究虚函数的实现道理,提议一下内容就不要看了。

        只要一个类有虚函数(继承下来的或者是本身的),它就会有一个vptr,vptr是一个指针,执行一个vbtl虚函数表。你可以把vbtl设想成一个指针数组,它的数组的元素是一个个的函数指针,指向它自己的虚函数,一般还会有一个指向typeinfo的结构的指针,以实现运行时刻类型识别。

        简略说来,现在CRect有一个虚函数display,那么他的vbtl会有两个元素,一个是typeinfo指针,一个是dispaly方法指针。

        2)简略看看typeinfo:

        CShape* pShape = &rect1;

        if(typeid(rect1) == typeid(CRect) && typeid(*pShape) == typeid(CRect)){

        TRACE_FUCTION_AND_LINE("rect1 is type CRect");

        TRACE_FUCTION_AND_LINE("rect1 name:(%s) raw_name:(%s)", typeid(rect1).name(), typeid(rect1).raw_name());

        }

        通过上述代码可以清晰看到可以在运行时候判断一个对象时什么类型,即使将rect1的地址转换父类指针,仍然可以判断出它实际是什么类型。

        typeinfo是一个类一个,编译器编译的时候静态静态分配,每一个带有虚函数的类的对象都市有一个指针指向它,同一个类的对象指针应当一致上面开始测试这一猜想。

        看有些书上说的是typeinfo的结构指针位于vtbl虚表的第0个item上,但是我在vc++编译调试没能找到,第0个item上是虚函数display的地址。

        于是又改在仿linux环境,MINGW下测试,在第-1个item选项上找到了typeinfo的地址,很是欣慰。但是在windows上还是一直找不到,猜测是在-1的item选项上,不过这个选项应当还有其他的货色,我这么揣摸的主要原因是除了-1和0item这两个位置,它们的前后地址都是0. 只是-1这个item的位置应当被微软又封装了一下,而不是单纯的指向typeinfo结构。

        以下为测试代码:

        const type_info* ptypeidinfo = &(typeid(rect1));

        TRACE_FUCTION_AND_LINE("ptypeidinfo: %p", ptypeidinfo);

        int *p = (int*)&rect1;

        int *pp = (int*)(p[0]);//vptr

        type_info *prectinfo = (type_info*) (*(pp-1));//pp-0: virtual function address

        TRACE_FUCTION_AND_LINE("prectinfo: %p", prectinfo);

        vs2008输出:

        main:  36] ptypeidinfo: 003A9004

        main:  40] prectinfo: 003A7748

        MINGW输出:

        main:  36] ptypeidinfo: 004085a4

        main:  40] prectinfo: 004085a4

        在vs2008上对pp-2和pp+1都查看了,都是0地址,因此可以猜测typeinfo确定跟pp-1有关,只是被封装了而已。

        3)回到问题 (上面的例子没有斟酌带有虚函数的情况):

        父类和子类都有一个虚函数,这样每一个子类对象的父类部分也就主动向下偏移,因此可以看到现在的fakerect是第二个int变量被转变了,这个比拟容易懂得,对于SetColor方法来说,它的参数是CShape,那么他就CShape的m_color的偏移地址(此处为4)停止赋值。试想另外一种情况,父类没有虚函数,子类有虚函数,这样父类占用的内存为4,子类的内存占用仍有vptr的4个字节内存。

        测试:可以将父类的display虚函数注释失落,打印结果如下:

        fakerect:1, 1, 1, 1

        fakerect:1, 20, 1, 1

        此时父类对象部分就不是位于子类对象起始部分,子类对象最起始是vptr,而SetColor还根据它本身的参数CShape类型来停止偏移。对于CShape来说,它看到的仅仅是四个字节的m_color,它对m_color停止赋值的时候,就会对pShape指针的最开始四个字节赋值,然而传递从前的fackrect对象的首地址却是fake1变量,而被转变的确是fake2变量,这个究竟是怎么回事呢??

        传递从前的首地址是fake1,SetColor转变也是传递从前的首地址,而最终被转变的是fake2,这个确定是有一些外部转换机制在作怪,猜测应当讲传递从前的( CRect* pfakerect = (CRect*)&fakerect; pfakerect->SetColor(20);) pfakerect指针在被强转为CShape*的时候地址被主动偏移了4个字节,上面停止测试:

        CShape类的SetColor方法,打印this指针地址:

        void CShape::SetColor(int colore)

        {

        //TRACE_FUCTION_AND_LINE("");

        TRACE_FUCTION_AND_LINE("pShape:%p", this);

        m_color = colore;

        }

        main.cpp程序,打印传递从前的pfakerect地址:

        CRect* pfakerect = (CRect*)&fakerect;

        TRACE_FUCTION_AND_LINE("fakerect:%p", pfakerect);

        pfakerect->SetColor(20);

        输出结果:

               main:  27] fakerect:002DF958

        CShape::SetColor:  12] pShape:002DF95C

     可以清晰的看到传递从前的地址是002DF958,而setColor在运行的时候处理的首地址却是002DF95C,这个应当是pfakerect转化为pShape时候的主动偏移的。

     为了更清晰的感觉到这种变化,现在直接在main.cpp参加测试代码:

        main.cpp:

        CRect* pfakerect = (CRect*)&fakerect;

        TRACE_FUCTION_AND_LINE("fakerect:%p", pfakerect);

        pfakerect->SetColor(20);

        CShape* pfakeshape = pfakerect;

        TRACE_FUCTION_AND_LINE("pfakeshape:%p", pfakeshape);

        输出结果如下:

               main:  27] fakerect:003EF77C

        CShape::SetColor:  12] pShape:003EF780

               main:  30] pfakeshape:003EF780

        可以看出fakerect的地址为003EF77C,在SetColor的时候停止强转的时候偏移了四个字节,当然我们通过CShape* pfakeshape = pfakerect直接停止强转的时候,还是会偏移4个字节。

        结论:

        每日一道理
    “多难兴才”曾一度被人定为规律。请看:屈原被放逐而作《离骚》;司马迁受宫刑而作《史记》;欧阳修两岁丧父笃学而成才;曹雪芹举家食粥而写出了不朽的《红楼梦》;越王勾践卧薪尝胆而雪洗国耻;韩信遭胯下辱而统率百万雄兵……他们都是在与逆境搏斗中成为伟人的!

        对于SetColor这种很纯粹的方法来说,它位于CShape类,它仅仅看到CShape类的成员,停止成员赋值的时候,它也是根据自身的类的描述结果停止赋值,但是在将CRect*赋值给CShape*的时候,编译器会查看这个类的描述,发现CShape类没有虚函数,CRect类有虚函数,因此在CRect*转换为CShape*的时,编译器会停止主动4个字节的偏移。我以为这个应当是在编译器编译的时候主动做好的。由于我现在还没有到达能看懂编译器对代码所生成的目标代码的地步,也就没有继续向下追踪,如果你可以读懂它生成的目标代码,可以跟踪一下,是不是对于pfakerect转换为pfakeshape这一句的目标代码中,pfakeshape已主动停止了偏移4个字节,我以为应当是编译器编译期的行为。

        想做一个简略总结了:

        OK,通过以上描述(说的比拟乱,本身语言表达能力不行,还是边写边思考边测试的,望你懂得,有问题可留言),上面的基本的讨论大部分都是针对一般成员变量m_color的赋值,非虚函数SetColor的运行机制。

        1,首先报告了继承的父类和子类的内存排布,主要包括了一般成员变量,vptr,以及字节对齐的内存(欲验证字节对齐的友人,可以在CRect去失落虚函数再加一个double的成员变量就能够看到CRect的大小变化了。),也许你会问静态数据成员是不是位于对象里面,可以明白的答复你,不会,静态对象成员是位于类,不是位于对象的,因此不可能每一个对象存储一份copy。上面的三种其实就是整个对象的内存占用。 

        2,然后报告了一般成员函数SetColor的运行机制,它被编译成的目标代码实际上是以C的机制停止的,在停止rect1.SetColor(10);这样的调用的时候,实际上也是以C的方式停止的调用SetColor(10, &rect1),这个通过参加一个一般的SetColor函数,与它比较做了验证,还测试了时间消费,也相差不大。还讨论了通过fakerect也确定SetColor的运行机制,它就能够当成很一般的C赋值,不管你传入的实际类型是什么,它所要做的就是对传递过去的地址停止一定m_color的偏移,然后赋值。

        3,然后又讨论了拓展延伸一部分,简略描述了虚函数,虚函数对于对象内存大小的影响。一不小心又扯远了,讲到了typeinfo结构,讲到了typeinfo同样是占用了vptr所指向的vbtl指针表的一个表项,可以通过类型的强转从vbtl中读取typeinfo的结构与通过typeid取到的结构的地址停止比较,讨论了Vs2008的不一致和MINGW的一致,之所以可以比拟,是基于一个类的typeinfo只有一份内存来停止的。每一个对象(含有虚函数的)会存有一个指针指向它(以实现运行期的动态类型识别),typeid可以获取到它(这个应当是编译器设置的,编译器就分配好typeinfo的内存,然后给typeid指令返回它的地址)。

        4,然后又讨论了父类对象无虚函数,子类对象有虚函数,在停止SetColor的时候,外部机制究竟是如何运行的。再次验证了对于SetColor来说,它只根据自己自身对象的CShape结果来决议对m_color所停止的偏移,对于父类没有虚函数,子类有虚函数,于是将CRect*赋值给CShape*时外部担任默许偏移了4个字节的地址,这样可以保证SetColor赋值时仅仅斟酌自身的CShape的结构,那么这个SetColor就能够在编译期根据自身的结构来对m_color停止自身的偏移,编译生成一份独一的目标代码,外部传递的参数来保证传递过去的有效性。于是我们看到了将CRect*结构传递给CShape*的时候,外部担任停止4个字节的偏移传递给SetColor的pShape,对于SetColor方法来说,它只斟酌自身的结构。还论述了这个是编译器的任务,不过仅仅是个人猜测,理论上会是这样,为了高效而言。

        5,简略测试了一下,对于偏移和不偏移的情况,cpu消耗时间基本雷同,可以大概确定是编译期停止的偏移,运行期停止偏移的话,时间消费应当会大的多。不过这个需要能不雅测到目标代码的大牛来查看确定。

        ---------------------------2013/5/12 12:22:33---------------------------------------------------------

        1,对虚函数的入神,让我禁不住再看看虚函数表:

        1)既然虚函数表中有一个虚函数位于上述的pp[0]的位置,那么我们能否掏出来尝试调用一下以验证一些正确性呢,说实话,有点分歧常理,不过上

        述的取typeinfo的时候,不就是那么的分歧常理吗?好货色就是用来折腾的。

        main.cpp参加以下代码:

        const type_info* ptypeidinfo = &(typeid(rect1));

        TRACE_FUCTION_AND_LINE("ptypeidinfo: %p", ptypeidinfo);

        int *p = (int*)&rect1;

        int *pp = (int*)(p[0]);//vptr

        type_info *prectinfo = (type_info*) (*(pp-1));//virtual function address

        TRACE_FUCTION_AND_LINE("prectinfo: %p", prectinfo);

        typedef void* pDisplayFunc(CRect* rect);

        TRACE_FUCTION_AND_LINE("test begin:");

        pDisplayFunc* pfunc = (pDisplayFunc*)(*pp);

        pfunc(&rect1);

        TRACE_FUCTION_AND_LINE("test end..");

        pp是vptr的值,它指向的是一个vtbl,因此可以掏出它的表的第0项,*pp就是它的第0项的值,它是一个函数指针,是指向display函数,因此根据上面的讨论可以判断它的C函数原型应当是void display(CRect*);的,故而将它强转为pDisplayFunc,然后通过pfunc(&rect1)停止调用,然后你会发现它真的就是调用的CRect的display方法。

        输出如下,可以看到确切调用的CRect的display方法:

        [                          main:  47] test begin:

        [                CRect::display:  44]

        [                          main:  50] test end..

        2)再玩点传参的吧

        Shape.h中CRect类再参加一个虚函数:

        virtual void display1(int i){

        TRACE_FUCTION_AND_LINE("i=%d", i);

        }

        main.cpp:

        typedef void* pDisplayFunc1(int i, CRect* rect);

        TRACE_FUCTION_AND_LINE("test begin:");

        pDisplayFunc1* pfunc1 = (pDisplayFunc1*)(*(pp+1));

        (*pfunc1)(8888, &rect1);

        TRACE_FUCTION_AND_LINE("test end..");

        前面关于虚函数表还没有具体说明,再加一个虚函数,它的位置是位于第1个item表项上,可以通过取*(pp+1)取得,调用传递的参数为8888.

        打印结果如下:

        [                          main:  53] test begin:

        [               CRect::display1:  47] i=8888

        不过我的vs2008随后报告了内存拜访错误了,不晓得为什么,但是可以看到函数还是调用了,证明预想不错。采取MINGW仿linux倒没有问题正常打印出了结果,应当两个环境的安全检查程度不一样。

        3)再玩点C函数的感觉

        函数的地址应当是编译期确定,不晓得如何打印出来?

        再看个更洋气的:既然pfunc1是display1的函数地址,那么我完整就不需要传入&rect1的参数了,传递一个NULL验证猜想。在(*pfunc1)(8888, &rect1);上面再加上一个调用(pfunc1)(8899, NULL);   如我所愿,还是正常打印出了8899,

        打印结果(先注释失落前面那一句话,因为此方法引发内存拜访错误):

        [                          main:  53] test begin:

        [               CRect::display1:  47] i=8899

        4)继续新花样,更改vtbl看看效果。

        将vtbl的第0个item和第1个item的内容交换一下,那么再调用display会发生什么情况呢?迫不及待啊。

        很不幸,由于内存保护不让写,很是郁闷。但是有什么货色能够阻挡我们的兴趣呢?你保护,我自己制造一个rect,自己制造一个vtbl。基本思想是创立一个rectbuf的char数组保存rect1的内存,创立一个rectvtbl保存vtbl的项目,而且将rectbuf数组的前四个字节变成一个vptr指针指向rectvtbl的item0地址,由于这个vbtl表是我们自己的内存,因此是可以拜访的,然后交换一下rectvtbl表项的两个函数指针的值即可.

        测试中发现将display和display1交换,vs2008总是说内存拜访错误,猜想是因为两个函数一个有参数一个无参数导致的,于是又增加了一个虚函数display2,它和display1的内容一模一样。

        Shape.cpp:

        virtual void display2(int i){

        TRACE_FUCTION_AND_LINE("i=%d", i);

        }

        main.cpp:

        char rectbuf[sizeof(rect1)];

        memcpy(rectbuf, &rect1, sizeof(rect1));//分配内存保存rect1的内存

        char rectvtbl[sizeof(int*) * 4];

        memcpy(rectvtbl, pp-1, sizeof(int*)*4);//分配内存报错rectvtbl的内存

        int *prectbuf = (int*)rectbuf;

        int *pvtbl = (int*)((int*)rectvtbl + 1);//将pvtbl指向虚表的item0项目,因为前四个字节是typeinfo指针

        prectbuf[0] = (int)(pvtbl);

        CRect* fakecharRect = (CRect*)rectbuf;

        //fakecharRect->display();

        TRACE_FUCTION_AND_LINE("before deal: fakecharRect.display.....");

        fakecharRect->display1(100);

        fakecharRect->display2(100);//打印,正常调用。先调用display1,再调用display2

        long temp = *(pvtbl+1);

        *(pvtbl+1) = *(pvtbl+2);

        *(pvtbl+2) = temp;//交换虚表的item1和item2项目,也就是display1和display2

        TRACE_FUCTION_AND_LINE("after deal: fakecharRect.display.....");

        //fakecharRect->display();

        fakecharRect->display1(100);

        fakecharRect->display2(100);//打印,惊讶调用。先调用display2,再调用display1(交换表项目所致)

        输出结果如下:

        [                          main:  61] before deal: fakecharRect.display....

        [               CRect::display1:  54] i=100

        [               CRect::display2:  57] i=100

        [                          main:  69] after deal: fakecharRect.display.....

        [               CRect::display2:  57] i=100

        [               CRect::display1:  54] i=100

        [                 CRect::~CRect:  34]

        从日志打印的函数名字可以看到没有处理之前先调用diplay1,在调用display2。处理之后就变了。

        上面的测试可以验证几个结论,不要把对象之类的货色看的过于神秘了,对象就是内存,你可以通过CRect rect1来创立对象,你也可以通过char数组来创立对象,完整取决于你。函数是什么?函数就是一个地址而已,那个地址是编译时确定的。由于我们晓得了虚函数的一些机制,虚函数表项目中的内存实际代表的是哪一个函数,我们就能够通过交换表项目的内存,来停止一些匪夷所思的操纵,将display1函数实际上调用的是display2,如果你想,可以自己随便写一个函数,然后将函数地址赋值到表的项目中,然后当你停止类的diplay函数调用的时候,你会惊讶的发现它竟然调用的是一个外部C函数,这个绝对会另你周围的人惊讶不已。当然玩这个可不是 为了哗众取宠,懂得它可以让我们更懂得控制我们程序的行为,当程序涌现一些百思不得解的诡异状况时,仔细思考一下程序的外部执行逻辑,你就会豁然开朗(相信我,你碰到的一般都不会涉及到如此的深入)。

        想总结一下了:

        OK,通过虚函数的描述,让我们晓得了可以通过取虚函数表项目来停止函数调用,它可以如真实的C函数一样,我们可以通过更改虚函数的表项来操纵函数的调用,可以交换,可以给虚函数表项赋值。这样你将不再对虚函数感到很大的神秘性,它其实就是一个函数指针数组,里面存储着一堆以后类的虚函数而已。通过存储的虚函数可以到达运行时刻的动态调用。对于非虚函数,是编译器确定具体调用哪一个函数,因此会在目标代码中直接写死调用。还有前面说到不晓得取类的函数地址,前面突然想到了一个方式,测试了一下还行。

        代码如下:

        char buf[100];

        sprintf(buf, "%d", &(CRect::PrintMemory));//sprintf的目的主要是因为编译器编译期禁止我对函数指针强转为int,我绕了一个弯,做到了。

        *(pvtbl) = atoi(buf);

        TRACE_FUCTION_AND_LINE("after set CRect::PrintMemory function......");

        fakecharRect->display();//打印,惊讶调用。调用PrintMemory方法

        确定如此,调用的PrintMemory方法。此外我还简略测试了一下不同CRect对象的vptr,确切都是一样的,一个类一份内存。

        -------------------------------------2013/5/12 15:07:42------------------------------------------------

        虚函数的描述:

        虚函数就是一个函数,它为了实现运行时的多态和类型识别,于是引入了虚函数表和typeinfo。所谓运行多态,就是基类指针通过指向不同的派生类对象,可以触发不同的函数调用,而函数调用代码却一样,真正的外部处理的时候,它就去查它的虚函数表。虚函数仅仅发生在通过指着或者引用上,直接的对象的调用操纵不会引发虚函数。虚函数建立在基类的公有虚函数被派生类重写的情况,通过基类指针的不同指向,在运行期查表获取不同的函数执行。

        在前面讲到的指针的赋值时,提到派生类指针赋值给基类指针会有一些编译器隐含操纵。这让我不由对对象赋值产生一些兴趣。

        CShape shape1 = rect1;

        shape1.display();

        由于前面说过,虚函数仅仅发生在指针和引用中,可以确定是调用CShape的display方法,但是具体shape1的内存又是如何呢?

        先假设复制仅仅将rect1的基类部分切割给了shape1,也就是说shape1拥有了rect1的vptr,这个可以设想确定会有问题,甚至如果CShape没有虚函数的时候,shape1就相当于多了一个vptr的内存,可以预见当父类有vptr,编译器会默许写入父类的vptr,如果父类没有vptr,编译器会将子类的vptr切割失落。这些都测试通过了印证,代码很简略就不贴出来了,通过在父类参加一个PrintMemory方法,可以很容易不雅察到这种变化。

        未完待续:

    文章结束给大家分享下程序员的一些笑话语录: 苹果与谷歌之争就是封闭收费与自由免费思想之争。(别急着把google来膜拜哦?那可是一家公司,以赚钱为目标的公司!当年我Party就是这样把广大劳动人民吸引过来的。今天的结果你们都看到了。)

  • 相关阅读:
    tab下图片要求
    segmentController
    下拉加载更多
    获取用户信息
    时间选择器
    JavaScript标准库之 ----  Object
    JavaScript标准库之——JSON
    JavaScript标准库之
    post网络请求坑
    构造一个简单的Linux系统MenuOS
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3076912.html
Copyright © 2011-2022 走看看