C++的构造函数被禁止使用多态
为什么C++要这么规定呢?
这是为了避免调用虚函数的派生类版本的时候,使用了一些未被初始化的字段,从而引发崩溃。
如何绕过这个限制
- 自定义Init,在构造函数执行完之后执行
- 委托初始化
从汇编分析
#include <iostream>
class A
{
public:
A()
{
std::cout << "construction A" << std::endl;
print();
}
virtual void print()
{
std::cout << "print a" << std::endl;
}
private:
int a = 1;
};
class B : public A
{
public:
B()
{
std::cout << "construction B" << std::endl;
}
virtual void print()
{
std::cout << "print a" << std::endl;
}
};
int main()
{
A* p = new B;
p->print();
return 0;
}
int main()
{
004B68C0 push ebp
004B68C1 mov ebp,esp
004B68C3 push 0FFFFFFFFh
004B68C5 push 4B7EA7h
004B68CA mov eax,dword ptr fs:[00000000h]
004B68D0 push eax
004B68D1 sub esp,0E8h
004B68D7 push ebx
004B68D8 push esi
004B68D9 push edi
004B68DA lea edi,[ebp-34h]
004B68DD mov ecx,0Ah
004B68E2 mov eax,0CCCCCCCCh
004B68E7 rep stos dword ptr es:[edi]
004B68E9 mov eax,dword ptr [__security_cookie (04BC004h)]
004B68EE xor eax,ebp
004B68F0 push eax
004B68F1 lea eax,[ebp-0Ch]
004B68F4 mov dword ptr fs:[00000000h],eax
004B68FA mov ecx,offset _38199C8C_virtualTable@cpp (04BF029h)
004B68FF call @__CheckForDebuggerJustMyCode@4 (04B140Bh)
A* p = new B;
004B6904 push 8 //申请8字节地址空间
004B6906 call operator new (04B1140h)
004B690B add esp,4
004B690E mov dword ptr [ebp-0ECh],eax //返回值给局部变量
004B6914 mov dword ptr [ebp-4],0
004B691B cmp dword ptr [ebp-0ECh],0
004B6922 je __$EncStackInitStart+5Dh (04B6937h)
004B6924 mov ecx,dword ptr [ebp-0ECh] //new出来的地址传给ecx
004B692A call B::B (04B1285h) //B类的构造函数
004B692F mov dword ptr [ebp-0F4h],eax
004B6935 jmp __$EncStackInitStart+67h (04B6941h)
004B6937 mov dword ptr [ebp-0F4h],0
004B6941 mov eax,dword ptr [ebp-0F4h]
004B6947 mov dword ptr [ebp-0E0h],eax
004B694D mov dword ptr [ebp-4],0FFFFFFFFh
004B6954 mov ecx,dword ptr [ebp-0E0h]
004B695A mov dword ptr [p],ecx
p->print();
004B695D mov eax,dword ptr [p]
004B6960 mov edx,dword ptr [eax]
004B6962 mov esi,esp
004B6964 mov ecx,dword ptr [p]
004B6967 mov eax,dword ptr [edx]
004B6969 call eax
004B696B cmp esi,esp
004B696D call __RTC_CheckEsp (04B12F8h)
return 0;
004B6972 xor eax,eax
}
- new了8字节的地址空间
- new出来的地址传给ecx,调用B的构造函数
class B : public A
{
public:
B()
004B2100 push ebp
004B2101 mov ebp,esp
004B2103 sub esp,0CCh
004B2109 push ebx
004B210A push esi
004B210B push edi
004B210C push ecx
004B210D lea edi,[ebp-0Ch]
004B2110 mov ecx,3
004B2115 mov eax,0CCCCCCCCh
004B211A rep stos dword ptr es:[edi]
004B211C pop ecx
004B211D mov dword ptr [this],ecx //对象地址传给this
004B2120 mov ecx,offset _38199C8C_virtualTable@cpp (04BF029h)
004B2125 call @__CheckForDebuggerJustMyCode@4 (04B140Bh)
004B212A mov ecx,dword ptr [this] //this传给ecx
004B212D call A::A (04B1474h) //调用A的构造函数
004B2132 mov eax,dword ptr [this]
004B2135 mov dword ptr [eax],offset B::`vftable' (04B9B64h)
{
std::cout << "construction B" << std::endl;
004B213B mov esi,esp
004B213D push offset std::endl<char,std::char_traits<char> > (04B1041h)
004B2142 push offset string "construction B" (04B9B68h)
004B2147 mov eax,dword ptr [__imp_std::cout (04BD0D4h)]
004B214C push eax
004B214D call std::operator<<<std::char_traits<char> > (04B1203h)
004B2152 add esp,8
004B2155 mov ecx,eax
004B2157 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04BD0A0h)]
004B215D cmp esi,esp
004B215F call __RTC_CheckEsp (04B12F8h)
}
004B2164 mov eax,dword ptr [this]
004B2167 pop edi
004B2168 pop esi
004B2169 pop ebx
004B216A add esp,0CCh
004B2170 cmp ebp,esp
004B2172 call __RTC_CheckEsp (04B12F8h)
004B2177 mov esp,ebp
004B2179 pop ebp
004B217A ret
- 调用A的构造函数
class A
{
public:
A()
004B2050 push ebp
004B2051 mov ebp,esp
004B2053 sub esp,0CCh
004B2059 push ebx
004B205A push esi
004B205B push edi
004B205C push ecx
004B205D lea edi,[ebp-0Ch]
004B2060 mov ecx,3
004B2065 mov eax,0CCCCCCCCh
004B206A rep stos dword ptr es:[edi]
004B206C pop ecx
004B206D mov dword ptr [this],ecx //ecx传给this
004B2070 mov ecx,offset _38199C8C_virtualTable@cpp (04BF029h)
004B2075 call @__CheckForDebuggerJustMyCode@4 (04B140Bh)
004B207A mov eax,dword ptr [this] //this 传给eax
004B207D mov dword ptr [eax],offset A::`vftable' (04B9B34h) //将对象的虚表地址指向A类的虚表地址,虚表里的地址函数指向 A::print()
virtual void print()
{
std::cout << "print a" << std::endl;
}
private:
int a = 1;
004B2083 mov eax,dword ptr [this]
004B2086 mov dword ptr [eax+4],1 //初始化成员变量
{
std::cout << "construction A" << std::endl;
004B208D mov esi,esp
004B208F push offset std::endl<char,std::char_traits<char> > (04B1041h)
004B2094 push offset string "construction A" (04B9B3Ch)
004B2099 mov eax,dword ptr [__imp_std::cout (04BD0D4h)]
004B209E push eax
004B209F call std::operator<<<std::char_traits<char> > (04B1203h)
004B20A4 add esp,8
004B20A7 mov ecx,eax
004B20A9 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04BD0A0h)]
004B20AF cmp esi,esp
004B20B1 call __RTC_CheckEsp (04B12F8h)
print();
004B20B6 mov ecx,dword ptr [this]
004B20B9 call A::print (04B14B0h) //这里注意没有从虚表中读取,编译器直接生成调用A::print
}
- 将对象的虚表地址指向A类的虚表地址,虚表里的地址函数指向 A::print()
- 初始化成员变量
- 打印字符串
- 调用A::print (这里注意没有从虚表中读取,编译器直接生成调用A::print)
关键指令截取
int main()
004B6904 push 8 //对象大小
004B6906 call operator new (04B1140h) //申请对象内存
004B690B add esp,4
004B690E mov dword ptr [ebp-0ECh],eax
004B6924 mov ecx,dword ptr [ebp-0ECh] //对象地址
004B692A call B::B (04B1285h) //B的构造函数
B::B(void)
004B211D mov dword ptr [this],ecx //this指针
004B212A mov ecx,dword ptr [this] //对象地址
004B212D call A::A (04B1474h) //A构造函数
A::A(void)
004B206D mov dword ptr [this],ecx //this指针
004B207A mov eax,dword ptr [this] //对象地址
004B207D mov dword ptr [eax],offset A::`vftable' (04B9B34h) //对象的前四个字节指向A类的虚表
虚表的第一个函数就是A::print
A::A(void)
int a = 1;
004B2083 mov eax,dword ptr [this]
004B2086 mov dword ptr [eax+4],1 //初始化成员变量
std::cout << "construction A" << std::endl;
.......
print();
004B20B6 mov ecx,dword ptr [this]
004B20B9 call A::print (04B14B0h) //调用A::print (这里注意没有从虚表中读取,编译器直接生成调用A::print)
B::B(void)
004B212D call A::A (04B1474h) //A构造函数,上面已经分析过了
004B2132 mov eax,dword ptr [this]
004B2135 mov dword ptr [eax],offset B::`vftable' (04B9B64h) //对象的前四个字节指向B类的虚表
虚表中的第一个函数指向B::print()
B::B(void)
std::cout << "construction B" << std::endl;
............
int main()
004B6904 push 8 //对象大小
004B6906 call operator new (04B1140h) //申请对象内存
004B690B add esp,4
004B690E mov dword ptr [ebp-0ECh],eax
........
004B692A call B::B (04B1285h)
004B6954 mov ecx,dword ptr [ebp-0E0h] //对象地址
004B695A mov dword ptr [p],ecx
p->print();
004B695D mov eax,dword ptr [p]
004B6960 mov edx,dword ptr [eax] //读取虚表地址
004B6964 mov ecx,dword ptr [p] //this 传给ecx
004B6967 mov eax,dword ptr [edx] //虚表中的第一个函数地址
004B6969 call eax
- 现在对象前4个字节指向的是B类的虚表