• <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>

            兔子的技術(shù)博客

            兔子

               :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
              202 Posts :: 0 Stories :: 43 Comments :: 0 Trackbacks

            留言簿(10)

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            撰文:Don Clugston

            翻譯:周翔

            (接上篇)

            成員函數(shù)指針——為什么那么復(fù)雜?

            類(lèi)的成員函數(shù)和標(biāo)準(zhǔn)的C函數(shù)有一些不同。與被顯式聲明的參數(shù)相似,類(lèi)的成員函數(shù)有一個(gè)隱藏的參數(shù)this,它指向一個(gè)類(lèi)的實(shí)例。根據(jù)不同的編譯器,this或者被看作內(nèi)部的一個(gè)正常的參數(shù),或者會(huì)被特別對(duì)待(比如,在VC++中,this一般通過(guò)ECX寄存器來(lái)傳遞,而普通的成員函數(shù)的參數(shù)被直接壓在堆棧中)。this作為參數(shù)和其他普通的參數(shù)有著本質(zhì)的不同,即使一個(gè)成員函數(shù)受一個(gè)普通函數(shù)的支配,在標(biāo)準(zhǔn)C++中也沒(méi)有理由使這個(gè)成員函數(shù)和其他的普通函數(shù)(ordinary function)的行為相同,因?yàn)闆](méi)有thiscall關(guān)鍵字來(lái)保證它使用像普通參數(shù)一樣正常的調(diào)用規(guī)則。成員函數(shù)是一回事,普通函數(shù)是另外一回事(Member functions are from Mars, ordinary functions are from Venus)。

            你可能會(huì)猜測(cè),一個(gè)成員函數(shù)指針和一個(gè)普通函數(shù)指針一樣,只是一個(gè)代碼指針。然而這種猜測(cè)也許是錯(cuò)誤的。在大多數(shù)編譯器中,一個(gè)成員函數(shù)指針要比一個(gè)普通的函數(shù)指針要大許多。更奇怪的是,在Visual C++中,一個(gè)成員函數(shù)指針可以是4812甚至16個(gè)字節(jié)長(zhǎng),這取決于它所相關(guān)的類(lèi)的性質(zhì),同時(shí)也取決于編譯器使用了怎樣的編譯設(shè)置!成員函數(shù)指針比你想象中的要復(fù)雜得多,但也不總是這樣。

            讓我們回到二十世紀(jì)80年代初期,那時(shí),最古老的C++編譯器CFront剛剛開(kāi)發(fā)完成,那時(shí)C++語(yǔ)言只能實(shí)現(xiàn)單一繼承,而且成員函數(shù)指針剛被引入,它們很簡(jiǎn)單:它們就像普通的函數(shù)指針,只是附加了額外的this作為它們的第一個(gè)參數(shù),你可以將一個(gè)成員函數(shù)指針轉(zhuǎn)化成一個(gè)普通的函數(shù)指針,并使你能夠?qū)@個(gè)額外添加的參數(shù)產(chǎn)生足夠的重視。

            這個(gè)田園般的世界隨著CFront 2.0的問(wèn)世被擊得粉碎。它引入了模版和多重繼承,多重繼承所帶來(lái)的破壞造成了成員函數(shù)指針的改變。問(wèn)題在于,隨著多重繼承,調(diào)用之前你不知道使用哪一個(gè)父類(lèi)的this指針,比如,你有4個(gè)類(lèi)定義如下:

            class A {

            public:

            virtual int Afunc() { return 2; };

            };

            class B {

            public:

            int Bfunc() { return 3; };

            };

            // C是個(gè)單一繼承類(lèi),它只繼承于A

            class C: public A {

            public:

            int Cfunc() { return 4; };

            };

            // D 類(lèi)使用了多重繼承

            class D: public A, public B {

            public:

            int Dfunc() { return 5; };

            };

            假如我們建立了C類(lèi)的一個(gè)成員函數(shù)指針。在這個(gè)例子中,AfuncCfunc都是C的成員函數(shù),所以我們的成員函數(shù)指針可以指向Afunc或者Cfunc。但是Afunc需要一個(gè)this指針指向C::A(后面我叫它Athis),而Cfunc需要一個(gè)this指針指向C(后面我叫它Cthis)。編譯器的設(shè)計(jì)者們?yōu)榱颂幚磉@種情況使用了一個(gè)把戲(trick):他們保證了A類(lèi)在物理上保存在C類(lèi)的頭部(即C類(lèi)的起始地址也就是一個(gè)A類(lèi)的一個(gè)實(shí)例的起始地址),這意味著Athis == Cthis。我們只需擔(dān)心一個(gè)this指針就夠了,并且對(duì)于目前這種情況,所有的問(wèn)題處理得還可以。

            現(xiàn)在,假如我們建立一個(gè)D類(lèi)的成員函數(shù)指針。在這種情況下,我們的成員函數(shù)指針可以指向AfuncBfuncDfunc。但是Afunc需要一個(gè)this指針指向D::A,而Bfunc需要一個(gè)this指針指向D::B。這時(shí),這個(gè)把戲就不管用了,我們不可以把A類(lèi)和B類(lèi)都放在D類(lèi)的頭部。所以,D類(lèi)的一個(gè)成員函數(shù)指針不僅要說(shuō)明要指明調(diào)用的是哪一個(gè)函數(shù),還要指明使用哪一個(gè)this指針。編譯器知道A類(lèi)占用的空間有多大,所以它可以對(duì)Athis增加一個(gè)delta = sizeof(A)偏移量就可以將Athis指針轉(zhuǎn)換為Bthis指針。

            如果你使用虛擬繼承(virtual inheritance),比如虛基類(lèi),情況會(huì)變得更糟,你可以不必為搞懂這是為什么太傷腦筋。就舉個(gè)例子來(lái)說(shuō)吧,編譯器使用虛擬函數(shù)表virtual function table——“vtable”)來(lái)保存每一個(gè)虛函數(shù)、函數(shù)的地址和virtual_delta將當(dāng)前的this指針轉(zhuǎn)換為實(shí)際函數(shù)需要的this指針時(shí)所要增加的位移量。

            綜上所述,為了支持一般形式的成員函數(shù)指針,你需要至少三條信息:函數(shù)的地址,需要增加到this指針上的delta位移量,和一個(gè)虛擬函數(shù)表中的索引。對(duì)于MSVC來(lái)說(shuō),你需要第四條信息:虛擬函數(shù)表(vtable)的地址。

            成員函數(shù)指針的實(shí)現(xiàn)

            那么,編譯器是怎樣實(shí)現(xiàn)成員函數(shù)指針的呢?這里是對(duì)不同的326416位的編譯器,對(duì)各種不同的數(shù)據(jù)類(lèi)型(有intvoid*數(shù)據(jù)指針、代碼指針(比如指向靜態(tài)函數(shù)的指針)、在單一(single-)繼承、多重(multiple-)繼承、虛擬(virtual-)繼承和未知類(lèi)型(unknown)的繼承下的類(lèi)的成員函數(shù)指針)使用sizeof運(yùn)算符計(jì)算所獲得的數(shù)據(jù):

            編譯器

            選項(xiàng)

            int

            DataPtr

            CodePtr

            Single

            Multi

            Virtual

            Unknown

            MSVC

            4

            4

            4

            4

            8

            12

            16

            MSVC

            /vmg

            4

            4

            4

            16#

            16#

            16#

            16

            MSVC

            /vmg /vmm

            4

            4

            4

            8#

            8#

            --

            8#

            Intel_IA32

            4

            4

            4

            4

            8

            12

            12

            Intel_IA32

            /vmg /vmm

            4

            4

            4

            4

            8

            --

            8

            Intel_Itanium

            4

            8

            8

            8

            12

            20

            20

            G++

            4

            4

            4

            8

            8

            8

            8

            Comeau

            4

            4

            4

            8

            8

            8

            8

            DMC

            4

            4

            4

            4

            4

            4

            4

            BCC32

            4

            4

            4

            12

            12

            12

            12

            BCC32

            /Vmd

            4

            4

            4

            4

            8

            12

            12

            WCL386

            4

            4

            4

            12

            12

            12

            12

            CodeWarrior

            4

            4

            4

            12

            12

            12

            12

            XLC

            4

            8

            8

            20

            20

            20

            20

            DMC

            small

            2

            2

            2

            2

            2

            2

            2

            DMC

            medium

            2

            2

            4

            4

            4

            4

            4

            WCL

            small

            2

            2

            2

            6

            6

            6

            6

            WCL

            compact

            2

            4

            2

            6

            6

            6

            6

            WCL

            medium

            2

            2

            4

            8

            8

            8

            8

            WCL

            large

            2

            4

            4

            8

            8

            8

            8

            注:

            # 表示使用__single/__multi/__virtual_inheritance關(guān)鍵字的時(shí)候代表4812

            這些編譯器是Microsoft Visual C++ 4.0 to 7.1 (.NET 2003), GNU G++ 3.2 (MingW binaries, http://www.mingw.org/), Borland BCB 5.1 (http://www.borland.com/), Open Watcom (WCL) 1.2 (http://www.openwatcom.org/), Digital Mars (DMC) 8.38n (http://www.digitalmars.com/), Intel C++ 8.0 for Windows IA-32, Intel C++ 8.0 for Itanium, (http://www.intel.com/), IBM XLC for AIX (Power, PowerPC), Metrowerks Code Warrior 9.1 for Windows (http://www.metrowerks.com/),  Comeau C++ 4.3 (http://www.comeaucomputing.com/). Comeau的數(shù)據(jù)是在它支持的32位平臺(tái)(x86, Alpha, SPARC等)上得出的。16位的編譯器的數(shù)據(jù)在四種DOS配置(tiny, compact, medium,  large)下測(cè)試得出,用來(lái)顯示各種不同代碼和數(shù)據(jù)指針的大小。MSVC/vmg的選項(xiàng)下進(jìn)行了測(cè)試,用來(lái)顯示“成員指針的全部特性”。(如果你擁有在列表中沒(méi)有出現(xiàn)的編譯器,請(qǐng)告知我。非x86處理機(jī)下的編譯器測(cè)試結(jié)果有獨(dú)特的價(jià)值。)

             

            看著表中的數(shù)據(jù),你是不是覺(jué)得很驚奇?你可以清楚地看到編寫(xiě)一段在一些環(huán)境中可以運(yùn)行而在另一些編譯器中不能運(yùn)行的代碼是很容易的。不同的編譯器之間,它們的內(nèi)部實(shí)現(xiàn)顯然是有很大差別的;事實(shí)上,我認(rèn)為編譯器在實(shí)現(xiàn)語(yǔ)言的其他特性上并沒(méi)有這樣明顯的差別。對(duì)實(shí)現(xiàn)的細(xì)節(jié)進(jìn)行研究你會(huì)發(fā)現(xiàn)一些奇怪的問(wèn)題。

            一般,編譯器采取最差的,而且一直使用最普通的形式。比如對(duì)于下面這個(gè)結(jié)構(gòu):

            // Borland (缺省設(shè)置Watcom C++.

            struct {

            FunctionPointer m_func_address;

            int m_delta;

            int m_vtable_index; //如果不是虛擬繼承,這個(gè)值為0

            };

            // Metrowerks CodeWarrior使用了稍微有些不同的方式。

            //即使在不允許多重繼承的Embedded C++的模式下,它也使用這樣的結(jié)構(gòu)!

            struct {

            int m_delta;

            int m_vtable_index; // 如果不是虛擬繼承,這個(gè)值為-1

            FunctionPointer m_func_address;

            };

            // 一個(gè)早期的SunCC版本顯然使用了另一種規(guī)則:

            struct {

            int m_vtable_index; //如果是一個(gè)非虛擬函數(shù)(non-virtual function),這個(gè)值為0

            FunctionPointer m_func_address; //如果是一個(gè)虛擬函數(shù)(virtual function),這個(gè)值為0

            int m_delta;

            };

            //下面是微軟的編譯器在未知繼承類(lèi)型的情況下或者使用/vmg選項(xiàng)時(shí)使用的方法:

            struct {

            FunctionPointer m_func_address;

            int m_delta;

            int m_vtordisp;

            int m_vtable_index; // 如果不是虛擬繼承,這個(gè)值為0

            };

            // AIX (PowerPC)IBMXLC編譯器:

            struct {

            FunctionPointer m_func_address; // 對(duì)PowerPC來(lái)說(shuō)是64

            int m_vtable_index;

            int m_delta;

            int m_vtordisp;

            };

            // GNU g++使用了一個(gè)機(jī)靈的方法來(lái)進(jìn)行空間優(yōu)化

            struct {

            union {

            FunctionPointer m_func_address; // 其值總是4的倍數(shù)

            int m_vtable_index_2; // 其值被2除的結(jié)果總是奇數(shù)

            };

            int m_delta;

            };

            對(duì)于幾乎所有的編譯器,deltavindex用來(lái)調(diào)整傳遞給函數(shù)的this指針,比如Borland的計(jì)算方法是:

            adjustedthis = *(this + vindex -1) + delta // 如果vindex!=0

            adjustedthis = this + delta // 如果vindex=0

            (其中,“*”是提取該地址中的數(shù)值,adjustedthis是調(diào)整后的this指針——譯者注)

            Borland使用了一個(gè)優(yōu)化方法:如果這個(gè)類(lèi)是單一繼承的,編譯器就會(huì)知道delta和vindex的值是0,所以它就可以跳過(guò)上面的計(jì)算方法。

            GNU編譯器使用了一個(gè)奇怪的優(yōu)化方法。可以清楚地看到,對(duì)于多重繼承來(lái)說(shuō),你必須查看vtable(虛擬函數(shù)表)以獲得voffset(虛擬函數(shù)偏移地址)來(lái)計(jì)算this指針。當(dāng)你做這些事情的時(shí)候,你可能也把函數(shù)指針保存在vtable中。通過(guò)這些工作,編譯器將m_func_addressm_vtable_index合二為一(即放在一個(gè)union中),編譯器區(qū)別這兩個(gè)變量的方法是使函數(shù)指針(m_func_address)的值除以2后結(jié)果為偶數(shù),而虛擬函數(shù)表索引(m_vtable_index_2)除以2后結(jié)果為奇數(shù)。它們的計(jì)算方法是:

            adjustedthis = this + delta

            if (funcadr & 1) //如果是奇數(shù)

            call (* ( *delta + (vindex+1)/2) + 4)

            else //如果是偶數(shù)

            call funcadr

            (其中, funcadr是函數(shù)地址除以2得出的結(jié)果。——譯者注)

            InterItanium編譯器(但不是它們的x86編譯器)對(duì)虛擬繼承(virtual inheritance)的情況也使用了unknown_inheritance結(jié)構(gòu),所以,一個(gè)虛擬繼承的指針有20字節(jié)大小,而不是想象中的16字節(jié)。

            // Itaniumunknown  virtual inheritance下的情況.

            struct {

            FunctionPointer m_func_address; //對(duì)Itanium來(lái)說(shuō)是64

            int m_delta;

            int m_vtable_index;

            int m_vtordisp;

            };

            我不能保證Comeau C++使用的是和GNU相同的技術(shù),也不能保證它們是否使用short代替int使這種虛擬函數(shù)指針的結(jié)構(gòu)的大小縮小至8個(gè)字節(jié)。最近發(fā)布的Comeau C++版本為了兼容微軟的編譯器也使用了微軟的編譯器關(guān)鍵字(我想它也只是忽略這些關(guān)鍵字而不對(duì)它們進(jìn)行實(shí)質(zhì)的相關(guān)處理罷了)。

            Digital Mars編譯器(即最初的Zortech C++到后來(lái)的Symantec C++)使用了一種不同的優(yōu)化方法。對(duì)單一繼承類(lèi)來(lái)說(shuō),一個(gè)成員函數(shù)指針僅僅是這個(gè)函數(shù)的地址。但涉及到更復(fù)雜的繼承時(shí),這個(gè)成員函數(shù)指針指向一個(gè)形式轉(zhuǎn)換函數(shù)(thunk function,這個(gè)函數(shù)可以實(shí)現(xiàn)對(duì)this指針的必要調(diào)整并可用來(lái)調(diào)用實(shí)際的成員函數(shù)。每當(dāng)涉及到多重繼承的時(shí)候,每一個(gè)成員函數(shù)的指針都會(huì)有這樣一個(gè)形式轉(zhuǎn)換函數(shù),這對(duì)函數(shù)調(diào)用來(lái)說(shuō)是非常有效的。但是這意味著,當(dāng)使用多重繼承的時(shí)候,子類(lèi)的成員函數(shù)指針向基類(lèi)成員函數(shù)指針的轉(zhuǎn)換就會(huì)不起作用了。可見(jiàn),這種編譯器對(duì)編譯代碼的要求比其他的編譯器要嚴(yán)格得多。

            很多嵌入式系統(tǒng)的編譯器不允許多重繼承。這樣,這些編譯器就避免了可能出現(xiàn)的問(wèn)題:一個(gè)成員函數(shù)指針就是一個(gè)帶有隱藏this指針參數(shù)的普通函數(shù)指針。

            微軟"smallest for class"方法的問(wèn)題

            微軟的編譯器使用了和Borland相似的優(yōu)化方法。它們都使單一繼承的情況具有最優(yōu)的效率。但不像Borland,微軟在缺省條件下成員函數(shù)指針省略了值為的指針入口(entry),我稱這種技術(shù)為“smallest for class”方法:對(duì)單一繼承類(lèi)來(lái)說(shuō),一個(gè)成員函數(shù)指針僅保存了函數(shù)的地址(m_func_address),所以它有4字節(jié)長(zhǎng)。而對(duì)于多重繼承類(lèi)來(lái)說(shuō),由于用到了偏移地址(m_delta),所以它有8字節(jié)長(zhǎng)。對(duì)虛擬繼承,會(huì)用到12個(gè)字節(jié)。這種方法確實(shí)節(jié)省空間,但也有其它的問(wèn)題。

            首先,將一個(gè)成員函數(shù)指針在子類(lèi)和基類(lèi)之間進(jìn)行轉(zhuǎn)化會(huì)改變指針的大小!因此,信息是會(huì)丟失的。其次,當(dāng)一個(gè)成員函數(shù)指針在它的類(lèi)定義之前聲明的時(shí)候,編譯器必須算出要分配給這個(gè)指針多少空間,但是這樣做是不安全的,因?yàn)樵诙x之前編譯器不可能知道這個(gè)類(lèi)的繼承方式。對(duì)Intel C++和早期的微軟編譯器來(lái)說(shuō),編譯器僅僅對(duì)指針的大小進(jìn)行猜測(cè),一旦在源文件中猜測(cè)錯(cuò)誤,你的程序會(huì)在運(yùn)行時(shí)莫名其妙地崩潰。所以,微軟的編譯器中增加了一些保留字:__single_inheritance, __multiple_inheritance, __virtual_inheritance,并增設(shè)了一些編譯器開(kāi)關(guān)(compiler switch),如/vmg,讓所有的成員函數(shù)指針有相同的大小,而對(duì)原本個(gè)頭小的成員函數(shù)指針的空余部分用0填充。Borland編譯器也增加了一些編譯器開(kāi)關(guān),但沒(méi)有增加新的關(guān)鍵字。Intel的編譯器可以識(shí)別Microsoft增加的那些關(guān)鍵字,但它在能夠找到類(lèi)的定義的情況下會(huì)對(duì)這些關(guān)鍵字不做處理。

            對(duì)于MSVC來(lái)說(shuō),編譯器需要知道類(lèi)的vtable在哪兒;通常就會(huì)有一個(gè)this指針的偏移量(vtordisp),這個(gè)值對(duì)所有這個(gè)類(lèi)中的成員函數(shù)來(lái)說(shuō)是不變的,但對(duì)每個(gè)類(lèi)來(lái)說(shuō)會(huì)是不同的。對(duì)于MSVC,經(jīng)調(diào)整過(guò)的this指針是在原this指針的基礎(chǔ)上經(jīng)過(guò)下面的計(jì)算得出的:

            if (vindex=0) //如果不是虛擬繼承(_virtual_inheritance

            adjustedthis = this + delta

            else //如果是

            adjustedthis = this + delta + vtordisp + *(*(this + vtordisp) + vindex)

            在虛擬繼承的情況下,vtordisp的值并不保存在__virtual_inheritance指針中,而是在發(fā)現(xiàn)函數(shù)調(diào)用的代碼時(shí),編譯器才將其相應(yīng)的匯編代碼“嵌”進(jìn)去。但是對(duì)于未知類(lèi)型的繼承,編譯器需要盡可能地通過(guò)讀代碼確定它的繼承類(lèi)型,所以,編譯器將虛擬繼承指針(virtual inheritance pointer)分為兩類(lèi)(__virtual_inheritance__unknown_inheritance)。

            理論上,所有的編譯器設(shè)計(jì)者應(yīng)該在MFP(成員函數(shù)指針)的實(shí)現(xiàn)上有所變革和突破。但在實(shí)際上,這是行不通的,因?yàn)檫@使現(xiàn)在編寫(xiě)的大量代碼都需要改變。微軟曾發(fā)表了一篇非常古老的文章(http://msdn.microsoft.com/archive/en-us/dnarvc/html/jangrayhood.asp)來(lái)解釋Visual C++運(yùn)作的實(shí)現(xiàn)細(xì)節(jié)。這篇文章是Jan Gray寫(xiě)的,他曾在1990年設(shè)計(jì)了Microsoft C++的對(duì)象模型。盡管這篇文章發(fā)表于1994年,但這篇文章仍然很重要——這意味著C++的對(duì)象模型在長(zhǎng)達(dá)15年的時(shí)間里(1990年到2004年)沒(méi)有絲毫改變。

            現(xiàn)在,我想你對(duì)成員函數(shù)指針的事情已經(jīng)知道得太多了。要點(diǎn)是什么?我已為你建立了一個(gè)規(guī)則。雖然各種編譯器的在這方面的實(shí)現(xiàn)方法有很大的不同,但是也有一些有用的共同點(diǎn):不管對(duì)哪種形式的類(lèi),調(diào)用一個(gè)成員函數(shù)指針生成的匯編語(yǔ)言代碼是完全相同的。有一種特例是使用了“smallest for class”技術(shù)的非標(biāo)準(zhǔn)的編譯器,即使是這種情況,差別也是很微小的。這個(gè)事實(shí)可以讓我們繼續(xù)探索怎樣去建立高性能的委托(delegate)。

            (待續(xù))


            轉(zhuǎn)自:http://blog.csdn.net/zhoulingj/archive/2004/07/23/49799.aspx

            posted on 2010-12-15 09:21 會(huì)飛的兔子 閱讀(419) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): C++及開(kāi)發(fā)環(huán)境
            久久91精品综合国产首页| 久久亚洲天堂| yellow中文字幕久久网| 欧美色综合久久久久久| 久久亚洲AV成人无码| 久久精品国产亚洲AV嫖农村妇女| 国产精品久久国产精麻豆99网站| 国产叼嘿久久精品久久| 99久久国产综合精品女同图片| 狠狠色婷婷综合天天久久丁香| 香蕉99久久国产综合精品宅男自| 久久亚洲私人国产精品vA | 久久久精品人妻一区二区三区蜜桃| 人妻无码αv中文字幕久久琪琪布| 精品久久人人做人人爽综合| 久久综合狠狠综合久久| 三级片免费观看久久| 狠狠色噜噜狠狠狠狠狠色综合久久| 久久久久久久女国产乱让韩| 久久精品国产一区二区电影| 7777久久亚洲中文字幕| 久久久久久久综合狠狠综合| 日本免费久久久久久久网站| 亚洲欧美日韩中文久久| 狠狠色丁香婷婷久久综合五月| 99久久精品费精品国产| 国产精品久久久久久福利漫画| 久久这里都是精品| 日韩一区二区三区视频久久| 99热精品久久只有精品| 久久无码中文字幕东京热| 久久久免费观成人影院| 精品99久久aaa一级毛片| 国内精品久久久久影院免费| 久久国产精品成人片免费| 人妻精品久久无码区| 亚洲AV日韩精品久久久久久久 | 国产精品久久久久久久| 久久精品麻豆日日躁夜夜躁| 久久国产精品无码HDAV| 久久福利青草精品资源站|