博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++虚继承1
阅读量:4105 次
发布时间:2019-05-25

本文共 22320 字,大约阅读时间需要 74 分钟。

昨天和同学对c++虚继承这部分 产生了一些争论,发觉自己对技术越来越浮躁了。不得不痛下决心。一看c++虚继承的内部实现(很浅很浅的看看)。       
        以下内容来自自己实验,希望各位大哥指点。当然要想获得权威的解释,看《Inside C++ Object Model》
        让我们从最简单的开始。以下测试代码。
代码
class
Base{
public
: Base() { printf(
"
Base construct!\n
"
); }
//
virtual void Test()=0;
virtual
void
f() { printf(
"
Base\n
"
); }
virtual
void
f2() { printf(
"
Base2\n
"
); }
virtual
void
f3() { printf(
"
Base3\n
"
); }
void
f4() { printf(
"
Base4\n
"
); }};
class
Derived:
public
Base{
public
: Derived() { printf(
"
Derived construct!\n
"
); }
virtual
void
f() { printf(
"
Derived\n
"
); }
virtual
void
f2() { printf(
"
Derived2\n
"
); }
virtual
void
f3() { printf(
"
Derived3\n
"
); }
void
f4() { printf(
"
Derived4\n
"
); }
/*
virtual void Test() { printf("test\n"); }
*/
};
int
main(){ Base
*
p
=
new
Base; p
->
f(); p
->
f2(); p
->
f3(); p
->
f4();
/*
Base *p = new Derived;
*/
p
=
new
Derived; p
->
f(); p
->
f2(); p
->
f3(); p
->
f4();
//
p->Test();
delete p;
return
0
;}
  以下是在我的环境下反汇编的部分代码。我的环境是vs2008 默认的Release。
代码
.text:
00401060
;
int
__cdecl main(
int
argc,
const
char
**
argv,
const
char
**
envp).text:
00401060
_main proc near ; CODE XREF: __tmainCRTStartup
+
10Ap.text:
00401060
.text:
00401060
argc
=
dword ptr
4
.text:
00401060
argv
=
dword ptr
8
.text:
00401060
envp
=
dword ptr 0Ch.text:
00401060
.text:
00401060
push esi.text:
00401061
push edi.text:
00401062
push
4
; unsigned
int
.text:
00401064
call
??
2
@YAPAXI@Z_0 ;
operator
new
(
uint
).text:
00401069
mov edi, ds:__imp__printf.text:0040106F mov esi, eax.text:
00401071
add esp,
4
.text:
00401074
test esi, esi.text:
00401076
jz
short
loc_40108A.text:
00401078
push offset aBaseConstruct ;
"
Base construct!\n
"
.text:0040107D mov dword ptr [esi], offset
??
_7Base@@6B@ ;
const
Base::`vftable
'
.text:
00401083
call edi ; __imp__printf.text:
00401085
add esp,
4
.text:
00401088
jmp
short
loc_40108C
 .text:0040107D mov dword ptr [esi], offset ??_7Base@@6B@ ; const Base::`vftable' 是关键,根据上面分析,将指向Base类
的虚表的指针保存到了向堆中分配的空间中,也就是 esi=**base_vtbl
代码
.text:0040108C mov eax, [esi].text:0040108E mov edx, [eax] ;这里就好理解了,eax
=*
base_vtbl,edx
=
base_vtbl.text:
00401090
mov ecx, esi.text:
00401092
call edx ;调用虚表中的第一个函数以下类推.text:
00401094
mov eax, [esi].text:
00401096
mov edx, [eax
+
4
].text:
00401099
mov ecx, esi.text:0040109B call edx.text:0040109D mov eax, [esi].text:0040109F mov edx, [eax
+
8
].text:004010A2 mov ecx, esi.text:004010A4 call edx.text:004010A6 push offset aBase4 ;
"
Base4\n
"
;这里看出了非虚函数的优势,效率高,直接调用函数.text:004010AB call edi ; __imp__printf
 
这是Base虚表内容
代码
.rdata:0040216C ;
const
Base::`vftable
'
.rdata:0040216C
??
_7Base@@6B@ dd offset
?
f@Base@@UAEXXZ ; DATA XREF: _main
+
1Do ;这里每个标号都指向相应函数.rdata:0040216C ; _main
+
62
o.rdata:0040216C ; Base::f(
void
).rdata:
00402170
dd offset
?
f2@Base@@UAEXXZ ; Base::f2(
void
).rdata:
00402174
dd offset
?
f3@Base@@UAEXXZ ; Base::f3(
void
).rdata:
00402178
dd offset
??
_R4Derived@@6B@ ;
const
Derived::`RTTI Complete Object Locator
'
;这个不懂
 
Base 还是比较简单的,让我们看Derived
代码
.text:004010BD push offset aBaseConstruct ;
"
Base construct!\n
"
.text:004010C2 mov dword ptr [esi], offset
??
_7Base@@6B@ ;
const
Base::`vftable
'
.text:004010C8 call edi ; __imp__printf.text:004010CA push offset aDerivedConstru ;
"
Derived construct!\n
"
.text:004010CF mov dword ptr [esi], offset
??
_7Derived@@6B@ ;
const
Derived::`vftable
'
.text:004010D5 call edi ; __imp__printf.text:004010D7 add esp,
8
 
可见在构造函数中和我们想象的完全一样,从基类开始,不过需要注意一点,最后esi=**Derived_vtbl
以后的代码完全和在基类中调用函数一致。看来在VS2008中,c++的虚表其实就是数组(原来居然还以为是链表,不过似乎也有的编译器
是用链表实现的)。这个例子的确不复杂,但是事实上却没有这么简单。看下一个稍微复杂一点的。
代码
class
A{
public
: A() { printf(
"
A construct\n
"
); }
virtual
void
f(){printf(
"
A_F\n
"
);}};
class
B{
public
: B() { printf(
"
B construct\n
"
); }
virtual
void
f(){printf(
"
B_F\n
"
);}
virtual
void
g(){printf(
"
B_G\n
"
);}};
class
C:
public
A,
public
B{
public
: C() { printf(
"
C construct\n
"
); }
void
f(){printf(
"
C_f\n
"
);}};
int
_tmain(
int
argc, _TCHAR
*
argv[]){ A
*
a
=
new
A; B
*
b
=
new
B; C
*
c
=
new
C; a
->
f(); b
->
f(); b
->
g(); c
->
f();
return
0
;}
 
     先不看结果,花几分钟思考一下,class C 的虚表结构是什么?
     首先看代码,发现在class C中首先有一点不同,这个是之前的在class A,classB,classC中都是默认构造函数的代码
代码
.text:
00401077
push
8
; unsigned
int
;以前class只放一个指针,现在2个了。.text:
00401079
call
??
2
@YAPAXI@Z_0 ;
operator
new
(
uint
).text:0040107E add esp,
4
.text:
00401081
test eax, eax.text:
00401083
jz
short
loc_40109D.text:
00401085
mov dword ptr [eax
+
4
], offset
??
_7B@@6B@ ;
const
B::`vftable
'
.text:0040108C mov dword ptr [eax], offset
??
_7C@@6BA@@@ ;
const
C::`vftable
'
{for `A
'
}.text:
00401092
mov dword ptr [eax
+
4
], offset
??
_7C@@6BB@@@ ;
const
C::`vftable
'
{for `B
'
}.text:
00401099
mov edi, eax.text:0040109B jmp
short
loc_40109F
 
这个是上面代码真正的反汇编代码,对比下,就可能对上面代码为什么有一个这么冗余的代码,似乎有些感觉了。
 
代码
.text:004010A6 push offset aAConstruct ;
"
A construct\n
"
.text:004010AB mov dword ptr [esi], offset
??
_7A@@6B@ ;
const
A::`vftable
'
.text:004010B1 call edi ; __imp__printf.text:004010B3 push offset aBConstruct ;
"
B construct\n
"
.text:004010B8 mov dword ptr [esi
+
4
], offset
??
_7B@@6B@ ;
const
B::`vftable
'
.text:004010BF call edi ; __imp__printf.text:004010C1 push offset aCConstruct ;
"
C construct\n
"
.text:004010C6 mov dword ptr [esi], offset
??
_7C@@6BA@@@ ;
const
C::`vftable
'
{for `A
'
}.text:004010CC mov dword ptr [esi
+
4
], offset
??
_7C@@6BB@@@ ;
const
C::`vftable
'
{for `B
'
}.text:004010D3 call edi ; __imp__printf.text:004010D5 add esp, 0Ch
 
下面的大部分容易理解,关键的是在class B的虚表中的f()。
代码
; [thunk]:
public
:
virtual
void
__thiscall C::f`adjustor{
4
}
'
(void)
?
f@C@@W3AEXXZ proc near ;这时ecx 也就是this是指向class B的sub ecx,
4
;这里很明显将原来的指向B:f(),指向了class C的虚表的开始部分。ecx放的是this指针jmp
?
f@C@@UAEXXZ ; C::f(
void
) ;这里顺理成章的变成了C::f(),this也在上部改变了
?
f@C@@W3AEXXZ endp
 
这里似乎就是传说中的“形式转换程序”,这个的确减少了虚表的体积。
再看后面的代码,函数调用的时候和之前完全一致,也就是在class C中定义的f(),虽然没有被显示的声明为virtual,但vs2008已经
把他默认当成虚函数调用了。至此,和同学的争论就此结束。

转载地址:http://eejsi.baihongyu.com/

你可能感兴趣的文章
db db2_monitorTool IBM Rational Performace Tester
查看>>
postgresql监控工具pgstatspack的安装及使用
查看>>
【JAVA数据结构】双向链表
查看>>
【JAVA数据结构】先进先出队列
查看>>
乘法逆元
查看>>
Objective-C 基础入门(一)
查看>>
通过mavlink实现自主航线的过程笔记
查看>>
Flutter Boost的router管理
查看>>
iOS开发支付集成之微信支付
查看>>
C++模板
查看>>
【C#】如何实现一个迭代器
查看>>
【C#】利用Conditional属性完成编译忽略
查看>>
SSM-CRUD(1)---环境搭建
查看>>
Nginx(2)---安装与启动
查看>>
springBoot(5)---整合servlet、Filter、Listener
查看>>
C++ 模板类型参数
查看>>
C++ 非类型模版参数
查看>>
图形学 图形渲染管线
查看>>
DirectX11 计时和动画
查看>>
DirectX11 光照与材质的相互作用
查看>>