• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            西城

            指尖代碼,手上年華

            聯系 聚合 管理
              20 Posts :: 0 Stories :: 62 Comments :: 0 Trackbacks
            這兩天看了兩篇關于C++虛函數表的文章,一篇是陳浩的,http://blog.csdn.net/haoel/article/details/1948051
            還有一篇是http://www.cnblogs.com/livingintruth/archive/2012/08/03/2620599.html。照著第二篇里的例子,
            在我的電腦上測試了一下,發現有不少自己不懂的地方,原作者也沒有講的太清楚,所以自己查了不少資料,將一些所得
            與大家分享。
            示例的代碼(有部分改動):

            #include <cstdio>
            #include <iostream>
            #include <typeinfo>
            using namespace std;
            class Point
            {
            public:
                Point()
                {
                    cout<<"Point constructor"<<endl;
                }
              
                virtual void func_hs()
                {
                    cout<<"Point::func_hs"<<endl;
                    printf("the address of this --func_hs:%p\n",&Point::func_hs);
                }
                virtual void  func_zzy()
                {
                    cout<<"Point::func_zzy"<<endl;
                    printf("the address of this --func_zzy:%p\n",&Point::func_zzy);
                }

                static void print()
                {
                    //相對地址,在虛表指針之后.0x4 0x8 0xc --------->point to member
                    printf("&Point::x=%p\n&Point::y=%p\n&Point::z=%p\n",
                           &Point::x,&Point::y,&Point::z);
                }

                void printThis()
                {
                    //float *
                    printf("&this->x=%p\n&this->y=%p\n&this->z=%p\n",
                           &this->x,&this->y,&this->z);
                }

                void printVt()
                {
                    printf("the address of object,this:%p\nthe address of vt:%p\n",
                           this,(void*)*(int*)this);
                }
                void callVtFuncs(int num=2)
                {
                    cout<<endl<<endl;
                    
                    typedef void (*Funp)(void);
                    
                    
                    for(int i=0;i<num;i++)
                    {
                        Funp funp=(Funp)*((int*)*(int*)this+i);
                        printf("%p\n",((int*)*(int*)this+i));
                        printf("Point::callVtFuncs=>address of this fun:%p\n",funp);
                        if(i==2||i==3)
                        {
                            continue;
                        }
                        funp();
                    }
                }

                void printVirtualFunAddress()
                {
                    cout<<endl<<endl;
                       printf("func_hs:%p\nfunc_zzy:%p\nfunc_zzzy:%p\n",
                          &Point::func_hs,&Point::func_zzy,
                          &Point::func_zzzy);
                    printf("%p\n",&Point::func_zzzy);
                    
                }
                virtual ~Point()
                {
                    // printf("%p\n",&Point::~Point);
                    cout<<"Point destructor"<<endl;
                }
                virtual void  func_zzzy()
                {
                    cout<<"Point::func_zzzy"<<endl;
                    printf("the address of this --func_zzzy:%p\n",&Point::func_zzzy);
                }

            protected:
                float x,y,z;
            };

                
            int main(int argc, char *argv[])
            {
                Point point;
                Point::print();
                point.printThis();
                point.printVt();
                point.callVtFuncs(5);
                point.printVirtualFunAddress();
                printf("sizeof func:%u\n",sizeof(&main));
                printf("%p\n",&main);
                
                printf("sizeof memfunc:%u\n",sizeof(&Point::printVirtualFunAddress));
                printf("%p\n",&Point::printVirtualFunAddress);
                
                printf("%p\n",&Point::func_zzzy);

                printf("sizeof virtmemfunc:%u\n",sizeof(&Point::func_zzzy));
                cout<<typeid(point).name()<<endl;
                
                return 0;
            }


            Point類里面包括virtual desctructor共有4個virtual functions.
            輸出結果如下:

            Point constructor
            &Point::x=0x4
            &Point::y=0x8
            &Point::z=0xc
            &this->x=0xbffff624
            &this->y=0xbffff628
            &this->z=0xbffff62c
            the address of object,this:0xbffff620
            the address of vt:0x8048fc0


            0x8048fc0
            Point::callVtFuncs=>address of this fun:0x8048a12
            Point::func_hs
            the address of this --func_hs:0x1
            0x8048fc4
            Point::callVtFuncs=>address of this fun:0x8048a64
            Point::func_zzy
            the address of this --func_zzy:0x5
            0x8048fc8
            Point::callVtFuncs=>address of this fun:0x8048c8e
            0x8048fcc
            Point::callVtFuncs=>address of this fun:0x8048cda
            0x8048fd0
            Point::callVtFuncs=>address of this fun:0x8048cf8
            Point::func_zzzy
            the address of this --func_zzzy:0x11


            func_hs:0x1
            func_zzy:(nil)
            func_zzzy:0x5
            0x11
            sizeof func:4
            0x804880c
            sizeof memfunc:8
            0x8048bdc
            0x11
            sizeof virtmemfunc:8

            data member那一塊比較簡單,沒有太大的問題。主要是virtual table這塊比較復雜 。第一個小問題就是
            順序的問題。實驗證明在vtable中虛函數指針的順序是按照聲明時的順序放置的。不管virtual desctructor
            在哪個位置,都會占據兩個slot,即有兩個virtual desctructor.至于為什么會有兩個,具體我也不是太清楚,
            后面在討論。
            我第一個有一個有疑問的地方就是關于typeinfo的問題。記得《Inside the c++ object model》中,Lippman
            提到的對象模型中,將typeinfo放置在vtable的第一個slot中,而根據上面例子來看,第一個slot就是所聲明
            的第一個virtual function.那么typeinfo存在何處?

            vtable 的首地址是0x8048fc0,而從0x8048fb0開始的內存地址信息如下:

            (gdb) x/16a 0x8048fb0
            0x8048fb0:      0x75253a63      0xa     0x0     0x8048fdc <_ZTI5Point>
            0x8048fc0 <_ZTV5Point+8>:       0x8048a12 <Point::func_hs()>    0x8048a64 <Point::func_zzy()>   0x8048c8e <Point::~Point()>   0x8048cda <Point::~Point()>
            0x8048fd0 <_ZTV5Point+24>:      0x8048cf8 <Point::func_zzzy()>  0x696f5035      0x746e  0x804a4c8 <_ZTVN10__cxxabiv117__class_type_infoE@@CXXABI_1.3+8>
            0x8048fe0 <_ZTI5Point+4>:       0x8048fd4 <_ZTS5Point>  0x3b031b01      0x98    0x12

            可以看出,在vtable之前也有關于Point類的信息,0x8048fbc處的值為
            0x8048fdc <_ZTI5Point>,而0x8048fdc處的值為:0x804a4c8 <_ZTVN10__cxxabiv117__class_type_infoE@@CXXABI_1.3+8,可以猜測這是與類型有關的。


            輸出中我在typeid(point)那一行設了斷點,進入函數之后可以看到:

            (gdb) s
            std::type_info::name (this=0x8048fdc)
                at /usr/lib/gcc/i686-pc-linux-gnu/4.7.1/../../../../include/c++/4.7.1/typeinfo:102
            102         { return __name[0] == '*' ? __name + 1 : __name; }
            (gdb) n
            5Point

            point的類型信息為5Point,而type_info::name傳入的參數信息就是0x8048fdc.再看用
            -fdump-class-hierarchy輸出的關于Point的類型信息:


            Vtable for Point
            Point::_ZTV5Point: 7u entries
            0     (int (*)())0
            4     (int (*)())(& _ZTI5Point)
            8     (int (*)())Point::func_hs
            12    (int (*)())Point::func_zzy
            16    (int (*)())Point::~Point
            20    (int (*)())Point::~Point
            24    (int (*)())Point::func_zzzy

            Class Point
               size=16 align=4
               base size=16 base align=4
            Point (0xb60019a0) 0
                vptr=((& Point::_ZTV5Point) + 8u)


            這樣的結果已經相當清楚。在g++的實現中,真正的typeinfo信息放置在vtable之后,其位置是通過vtable之前的地址內
            所包含的信息所指定。在整個關于Point類的這些信息里,起始位置為&Point::_ZTV5Point,其值為0x0,之后是關于類型
            信息,然后才是vtable的入口點。即 vptr=((& Point::_ZTV5Point) + 8u)。point類的this指針通過類型轉化后所解
            引用得到的值即使vptr.至于size=16很好理解,1個vptr+3個float.


            另一個詭異的信息就是point to member的輸出,單個輸出是,3個virtual function分別是0x1 0x5 0x11,而在
            printVirtualFunAddress中所輸出的信息卻很詭異,分別是0x1,nil,0x5,這就是為什么要在main函數中加入測試
            函數指針大小的原因,普通函數是4個字節,而成員函數的確是8個字節,不管是不是virtual function.所以在printf
            參數入棧的時候,要push兩次,將第二次push的結果當作了第二個參數的值,以此后推。所以會有上面的結果。

            最后就是那兩個desctructor的問題,在上面通過函數指針調用函數的時候要將他們都略過去。如果將virtual去掉,
            那么vtable中就沒有desctructor。
            下面是objdump出來的結果:objdump -d a.out|grep PointD

             8048932:    e8 51 03 00 00           call   8048c88 <_ZN5PointD1Ev>
             8048944:    e8 3f 03 00 00           call   8048c88 <_ZN5PointD1Ev>
            08048c88 <_ZN5PointD1Ev>:
             8048cc5:    74 0b                    je     8048cd2 <_ZN5PointD1Ev+0x4a>
            08048cd4 <_ZN5PointD0Ev>:
             8048ce0:    e8 a3 ff ff ff           call   8048c88 <_ZN5PointD1Ev>


            兩個函數中,main調用的是<_ZN5PointD1Ev>,這就是我們上面聲明的那個virtual desctrucotr.而且在內存布局中,
            <_ZN5PointD1Ev>的位置比較靠前,其在vtable中的位置也應在<_ZN5PointD0Ev>之前,而<_ZN5PointD0Ev>

            08048cd4 <_ZN5PointD0Ev>:
             8048cd4:    55                       push   %ebp
             8048cd5:    89 e5                    mov    %esp,%ebp
             8048cd7:    83 ec 18                 sub    $0x18,%esp
             8048cda:    8b 45 08                 mov    0x8(%ebp),%eax
             8048cdd:    89 04 24                 mov    %eax,(%esp)
             8048ce0:    e8 a3 ff ff ff           call   8048c88 <_ZN5PointD1Ev>
             8048ce5:    8b 45 08                 mov    0x8(%ebp),%eax
             8048ce8:    89 04 24                 mov    %eax,(%esp)
             8048ceb:    e8 80 f9 ff ff           call   8048670 <_ZdlPv@plt>
             8048cf0:    c9                       leave  

            的工作好像也要調用<_ZN5PointD1Ev>,這個應該是編譯器生成的,再往深處我也不甚清楚了。



            posted on 2012-08-10 11:17 西城 閱讀(2341) 評論(2)  編輯 收藏 引用 所屬分類: C/C++

            Feedback

            # re: 關于C++ 虛函數表的一些問題(g++) 2012-08-29 19:20 izualzhy
            static void print()
            {
            //相對地址,在虛表指針之后.0x4 0x8 0xc --------->point to member
            printf("&Point::x=%p\n&Point::y=%p\n&Point::z=%p\n",
            &Point::x,&Point::y,&Point::z);
            }

            靜態函數使用非靜態成員,為什么可以編譯通過啊?  回復  更多評論
              

            # re: 關于C++ 虛函數表的一些問題(g++) 2012-08-30 19:16 西城
            @izualzhy
            我又看了一下,確實是可以的。感覺&Point::x這種東西是與具體對象無關的,每個對象都一樣,所以我覺得從行為上來說應該是與靜態成員一樣。  回復  更多評論
              

            久久精品国产免费| 91麻豆国产精品91久久久| 99久久99这里只有免费的精品| 久久久久久久久久免免费精品| 国产精品99久久99久久久| 中文字幕无码久久精品青草| 精品午夜久久福利大片| 久久天天躁狠狠躁夜夜av浪潮| 97精品国产97久久久久久免费| 久久99国产综合精品| 品成人欧美大片久久国产欧美| 国产精品久久久久无码av| 久久精品综合一区二区三区| 热re99久久精品国99热| 青草国产精品久久久久久| 国产精品日韩深夜福利久久| 人妻少妇久久中文字幕| 亚洲国产精品狼友中文久久久 | 久久亚洲精品国产精品| 久久久久久A亚洲欧洲AV冫| 久久精品中文字幕无码绿巨人| 精品国产乱码久久久久久人妻| 亚洲乱码日产精品a级毛片久久| 久久精品国产久精国产思思| 日产久久强奸免费的看| 欧美精品丝袜久久久中文字幕| 久久99国产综合精品女同| 国产成年无码久久久免费| 久久精品人人槡人妻人人玩AV| 一本色道久久综合| 久久国产色av免费看| 色偷偷88欧美精品久久久| 久久93精品国产91久久综合| 久久精品国产99久久丝袜| 久久福利青草精品资源站| 久久精品国产亚洲AV无码麻豆| 乱亲女H秽乱长久久久| 久久人人爽人人爽人人片AV不| 久久久国产乱子伦精品作者| 久久久亚洲欧洲日产国码二区| 亚洲va中文字幕无码久久|