C++對象布局及多態(tài)實(shí)現(xiàn)之動態(tài)和強(qiáng)制轉(zhuǎn)換
從這部分開始我們除了利用內(nèi)存的信息打印來進(jìn)行探索外,更多的會通過跟蹤和觀察編譯器產(chǎn)生的匯編代碼來理解編譯器對這些語言特性的實(shí)現(xiàn)方式。匯編方面知識的討論超出了本文的范圍,我只對和我們討論相關(guān)的匯編代碼進(jìn)行解析。理解本文要討論的知識并不需要有很完整的匯編知識,但必須了解起碼的概念。
下面我們看看引入虛繼承后的影響。為了有所對比我們首先看看普通成員函數(shù)的調(diào)用情況。
執(zhí)行如下代碼,它包括了對象的普通成員函數(shù)調(diào)用,類的靜態(tài)成員函數(shù)調(diào)用、通過指針調(diào)用普通成員函數(shù):
C010 obj;PRINT_OBJ_ADR(obj)obj.foo();C012::sfoo();C010 * pt = &obj;pt-> foo();
結(jié)果如下:
obj's address is : 0012F843
這是obj對象的內(nèi)存地址。
首先我們看看對象的普通成員函數(shù)調(diào)用,obj.foo();,對應(yīng)的匯編代碼為:
00422E09 lea ecx,[ebp+FFFFF967h]00422E0F call 0041E289
第1行把對象的地址存入ecx寄存器,執(zhí)行完這行指令后,我們要以看到ecx中的值為0x0012F843,就是前面打印出的值。如果函數(shù)需要傳遞參數(shù),我們還會在前面看到一些push指令。在第2行我們可以看到call的是一個(gè)直接的地址,這也就是靜態(tài)綁定。即函數(shù)的調(diào)用地址在編譯時(shí)已經(jīng)被編譯器決議。
跟蹤進(jìn)去我們要以看到是一條跳轉(zhuǎn)指令,繼續(xù)執(zhí)行可以看到真正的函數(shù)代碼部分,如下(注:為了討論方便我在第行前面加了一個(gè)行號):
01 00425FE0 push ebp02 00425FE1 mov ebp,esp03 00425FE3 sub esp,0CCh04 00425FE9 push ebx05 00425FEA push esi06 00425FEB push edi07 00425FEC push ecx08 00425FED lea edi,[ebp+FFFFFF34h]09 00425FF3 mov ecx,33h10 00425FF8 mov eax,0CCCCCCCCh11 00425FFD rep stos dword ptr [edi]12 00425FFF pop ecx13 00426000 mov dword ptr [ebp-8],ecx14 00426003 mov eax,dword ptr [ebp-8]15 00426006 mov byte ptr [eax],216 00426009 pop edi17 0042600A pop esi18 0042600B pop ebx19 0042600C mov esp,ebp20 0042600E pop ebp21 0042600F ret
我們看看第7行,把ecx寄存器入棧,后面4行初始化了函數(shù)的堆棧中的保存局部變量的部分。第12行彈出ecx值,到這里時(shí)ecx的值保持為在函數(shù)調(diào)用前存入的對象內(nèi)存地址,第13行就是保存this指針的值,作為一個(gè)局部變量。這樣我們就知道了VC7.1不是象傳遞普通函數(shù)那樣通過壓棧來傳遞this 指針,而是通過ecx寄存器來傳遞。第14、15行利用這個(gè)this指針給對象的成員變量進(jìn)行了賦值。
再看看靜態(tài)成員函數(shù)調(diào)用的匯編代碼:
00422E14 call 0041DD84
非常直接,因?yàn)樗恍枰幚韙his指針,跟蹤到函數(shù)的匯編代碼,可以看到同樣不需要處理this指針。具體的代碼這里就不列出來了。
再看看通過指針調(diào)用普通成員函數(shù)pt-> foo();,產(chǎn)生的匯編代碼如下:
00422E25 mov ecx,dword ptr [ebp+FFFFF958h]00422E2B call 0041E289
和通過對象調(diào)用普通成員函數(shù)的代碼差不多。不過存對象地址到ecx寄存器地,是通過解引用pt指針來找到對象地址的。
來源:ks990次