C++类机制的实现细节
为了搞清楚VC中类的实现专门写了一个最简单的类,用来观察它的实现过程,代码如下:
// test.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include "CTest.h"
int main(int argc, char* argv[])
{
CTest aTest;
aTest.a(1,2);
return 0;
}
// CTest.h: inte易做图ce for the CTest class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_CTEST_H__2CCCDCFC_6C3A_48BC_9CD0_E7A8E63431D9__INCLUDED_)
#define AFX_CTEST_H__2CCCDCFC_6C3A_48BC_9CD0_E7A8E63431D9__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class CTest
{
public:
CTest();
virtual ~CTest();
public:
void b();
void a(int one,int two);
};
#endif // !defined(AFX_CTEST_H__2CCCDCFC_6C3A_48BC_9CD0_E7A8E63431D9__INCLUDED_)
// CTest.cpp: implementation of the CTest class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "CTest.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CTest::CTest()
{
}CTest::~CTest()
{
}
void CTest::b()
{
printf("b is be called by a");
}
void CTest::a(int one,int two)
{
printf("call b");
b();
}
下面是相应的反汇编代码:
--- D:myown est est.cpp ------------
1: // test.cpp : Defines the entry point for the console application.
2: //
3:
4: #include "stdafx.h"
5: #include "CTest.h"
6:
7: int main(int argc, char* argv[])
8: {
00401050 push ebp
00401051 mov ebp,esp
00401053 push 0FFh
00401055 push offset __ehhandler$_main (00410c89)
0040105A mov eax,fs:[00000000]
00401060 push eax
00401061 mov dword ptr fs:[0],esp
00401068 sub esp,48h
0040106B push ebx
0040106C push esi
0040106D push edi
0040106E lea edi,[ebp-54h]
00401071 mov ecx,12h
00401076 mov eax,0CCCCCCCCh
0040107B rep stos dword ptr [edi]
9: CTest aTest;
0040107D lea ecx,[ebp-10h] file://这是用来保存aTest的this指针,因为是局部变量所以是保存在[ebp-10h]中
00401080 call @ILT+30(CTest::CTest) (00401023) file://调用aTest的构造函数,由编译器自动产生的CALL
00401085 mov dword ptr [ebp-4],0
10: aTest.a(1,2);
0040108C push 2
0040108E push 1
00401090 lea ecx,[ebp-10h] file://把aTest的this指针用ecx进行传递
00401093 call @ILT+5(CTest::a) (0040100a)
11: return 0;
00401098 mov dword ptr [ebp-14h],0
0040109F mov dword ptr [ebp-4],0FFFFFFFFh
004010A6 lea ecx,[ebp-10h] file://同样是this指针
004010A9 call @ILT+25(CTest::~CTest) (0040101e) file://aTest的生存周期到了,自动调用析构函数,同样是由编译器分析之后自加上去
004010AE mov eax,dword ptr [ebp-14h]
12: }
004010B1 mov ecx,dword ptr [ebp-0Ch]
004010B4 mov dword ptr fs:[0],ecx
004010BB pop edi
004010BC pop esi
004010BD pop ebx
004010BE add esp,54h
004010C1 cmp ebp,esp
004010C3 call __chkesp (00401670)
004010C8 mov esp,ebp
004010CA pop ebp
004010CB ret
下面再来分析一下VC中对函数的调用:
可以看到上面有对三个函数的调用分别为:
00401080 call @ILT+30(CTest::CTest) (00401023)
00401093 call @ILT+5(CTest::a) (0040100a)
004010A9 call @ILT+25(CTest::~CTest) (0040101e)
可以看到他们都跳到了以@ILT为基的一个地址去了,那么跳过去之后可以看到:
@ILT+0(??_GCTest@@UAEPAXI@Z):
00401005 jmp CTest::`scalar deleting destructor (00401130)
@ILT+5(?a@CTest@@QAEXHH@Z):
0040100A jmp CTest::a (00401230)
@ILT+10(_main):
0040100F jmp main (00401050)
@ILT+15(?b@CTest@@QAEXXZ):
00401014 jmp CTest::b (004011e0)
@ILT+20(??_GCTest@@UAEPAXI@Z):
00401019 jmp CTest::`scalar deleting destructor (00401130)
@ILT+25(??1CTest@@UAE@XZ):
0040101E jmp CTest::~CTest (004011a0)
@ILT+30(??0CTest@@QAE@XZ):
00401023 jmp CTest::CTest (004010f0)
这个@ILT其实就是一个静态的表,它记录了一些函数的入口然后跳过去,每个跳转jmp占一个字节,然后就是一个四字节的内存地址,所以加起为五个字节,这样就实现了类的机制。
下面再来分析一下,类的成员函数调用另一成员函数的情况:
27: void CTest::a(int one,int two)28: {
00401230 push ebp
00401231 mov ebp,esp
00401233 sub esp,44h
00401236 push ebx
00401237 push esi
00401238 push edi
00401239 push ecx
0040123A lea edi,[ebp-44h]
0040123D mov ecx,11h
00401242 mov eax,0CCCCCCCCh
00401247 rep stos dword ptr [edi]
00401249 pop ecx
0040124A mov dword ptr [ebp-4],ecx
29: printf("call b");
0040124D push offset string "call b" (00422038)
00401252 call printf (00401830)
00401257 add esp,4
30: b();
0040125A mov ecx,dword ptr [ebp-4] file://不要想这里的[ebp-4]肯定是this指针,
0040125D call @ILT+15(CTest::b) (00401014) // 又是@ILT静态表格
31: }
00401262 pop edi
00401263 pop esi
00401264 pop ebx
00401265 add esp,44h
00401268 cmp ebp,esp
0040126A call __chkesp (00401670)
0040126F mov esp,ebp
00401271 pop ebp
00401272 ret 8 file://由于是STDCALLR所以栈是由程序自己来平衡的