现在的位置: 首页 > 综合 > 正文

c++底层剖析(1)

2013年07月21日 ⁄ 综合 ⁄ 共 2523字 ⁄ 字号 评论关闭

首先从带有虚函数的基类派生讲起。

class Base
{
public:
	Base(){base = 1; cout<<"Base!\n";}
	virtual ~Base(){cout<<"~Base!\n";}
	virtual void say(){cout<<"hello Base\n";}
	int base;
};

class Derive : public Base
{
public:
	Derive(){derive = 2;}
	virtual ~Derive(){cout<<"~Derive!\n";}
	virtual void say(){cout<<"hello Derive\n";}
	int derive;
};

int _tmain(int argc, _TCHAR* argv[])
{
	Base *pd=new Derive;
	pd->say();
	delete pd;
	return 0;
}

这是一个很简单的派生的例子,首先从内存角度看下,期中0x003B94B0为指针pd的值

可以看到最开始的四个字节的值为0x00417700, 后面是两个int型值分别为1和2,代表着base和derive的值,这两个值是在构造函数里进行赋值的,现在主要看下0x00417700这个值代表着什么?

0x00417700这个内存位置的值是0x004112c6,在继续跟下0x004112c6这个内存位置有什么东西

004112C6  jmp         Derive::`scalar deleting destructor' (414980h) 

可以看到这个地方是跳到一个好像是Derive的析构函数处,其实进去之后会发现其实他是一个代理函数,在此函数中先调用Derive的析构函数,然后在调用operator delete 释放内存。可以通过反汇编代码来看下:

Derive::`scalar deleting destructor':

00414980  push        ebp  
。。。。
0041499F  pop         ecx  
004149A0  mov         dword ptr [ebp-8],ecx 
004149A3  mov         ecx,dword ptr [this] 
004149A6  call        Derive::~Derive (4112B2h) 
004149AB  mov         eax,dword ptr [ebp+8] 
004149AE  and         eax,1 
004149B1  je          Derive::`scalar deleting destructor'+3Fh (4149BFh) 
004149B3  mov         eax,dword ptr [this] 
004149B6  push        eax  
004149B7  call        operator delete (4110D2h) 

中间的省略掉了,无关紧要,主要是将开辟的一段栈空间的内容初始化为0xcccccccc,在红色处可以看到Derive的析构函数的调用。现在在回到开头处,用一张图示意下

可以看到Derive类型的对象的内存布局,首先是一个虚表指针,然后是基类的成员变量最后才是自己的成员变量

现在来观看下这个指向虚表的指针式什么时候安插到对象的头部的,我们通过派生类的构造函数来看下:

Derive()

004116A0  push        ebp  
。。。。。。
004116BF  pop         ecx  
004116C0  mov         dword ptr [ebp-8],ecx    //ecx为this的值
004116C3  mov         ecx,dword ptr [this] 
004116C6  call        Base::Base (41115Eh) 
004116CB  mov         eax,dword ptr [this] 
004116CE  mov         dword ptr [eax],offset Derive::`vftable' (417700h) 

通过观看构造函数的反汇编代码可以看到,在派生类的构造函数中先是调用基类Base的构造函数,现在我们跟到基类的构造函数中去:

Base():

004117B0  push        ebp  
。。。。。
004117CF  pop         ecx  
004117D0  mov         dword ptr [ebp-8],ecx 
004117D3  mov         eax,dword ptr [this] 
004117D6  mov         dword ptr [eax],offset Base::`vftable' (417718h) 
004117DC  mov         eax,dword ptr [this] 
004117DF  mov         dword ptr [eax+4],1 
004117E6  push        offset string "Base!\n" (417708h) 
004117EB  mov         eax,dword ptr [__imp_std::cout (41B328h)] 
004117F0  push        eax  
004117F1  call        std::operator<<<std::char_traits<char> > (4111A4h) 

通过基类的构造函数可以看到,他将offset Base::`vftable' (417718h)基类的虚表地址赋给了对象的头四个字节,然后才是执行基类构造函数中的代码,现在我们可以看到pd头部的四个字节的值不是417700h而是417718h,奇怪了别急,后面会看到417700h这个值是赋给pd头部的四个字节。基类的构造函数执行完毕之后回到派生类的构造函数,此事可以看到紧接着就是两个赋值语句

004116CB  mov         eax,dword ptr [this] 
004116CE  mov         dword ptr [eax],offset Derive::`vftable' (417700h) 

首先是将this指针的值赋给eax,然后将offset Derive::`vftable' (417700h)派生类的虚表的指针赋给eax所指向的地址的头四个字节,也就是赋给pd所指的派生类对象的头四个字节,现在到此也就分析完了其内存布局,以及虚表指针的赋值过程。

抱歉!评论已关闭.