关于C++类组织形式的探索


一、
引言
1.
概述
    自从C++出现以来,C++就引出了许许多多的话题。本人学习C++也有一段时间了,前阵子,忽然对平时常用的某些C++特性感到陌生,特此做了一下试验,去探索一下,C++到底帮助我们做了些什么。

2.
测试平台
Microsoft Visual Studio .NET 2003


二、
试验步骤与结论
1.
一般函数的调用
(1)
试验步骤
    构建一个简单的无参无返回的空函数fun(),在主函数中调用

(2)
反汇编测试
    调用一个无参数无返回的函数,其汇编代码如下:


fun();


004138E5
call
fun (411573h)

接着,程序跳转至以下位置:
00411573
jmp
fun (413890h)

最后,程序进入函数体内部。

(3)
分析
    可见调用一个一般的函数,经过两级跳转,第一次跳转至一个函数表,第二次跳至函数体内部。


2.
类一般函数的调用
(1)
类的构建

class A


{


public:



A()



{



i=1;



}



void f()



{



printf("A::f()\n");



}



virtual void g()



{



printf("A::g()\n");



}


private:



int i;


};

(2)
试验步骤
    在主函数中初始化A的两个实例对象a1a2,调用A的函数f()
(3)
反汇编测试
调用实例对象的类函数,其汇编代码如下


a1.f();


00411C5E
lea
ecx,[a1]


00411C61
call
A::f (41100Fh)



a2.f();


00411C66
lea
ecx,[a2]


00411C69
call
A::f (41100Fh)

经过第一次跳转后,进入代码段:
0041100F
jmp
A::f (411AF0h)

最后进入函数体内部。

(4)
分析
    我们可以看到,对类实例对象函数的调用,与一般函数调用方法基本一致。另外我们可以通过反汇编代码看出,对于类的实例对象而言,类函数是共享的。

3.
类虚函数的调用
(1)
类的构建
我们构建一个A的继承类B

class B:public A


{


public:



B()



{



j=2;



}



void f()



{



printf("B::f()\n");



}



virtual void g()



{



printf("B::g()\n");



}



virtual void h()



{



printf("B::h()\n");



}



int j;


};

(2)
试验步骤
我们建立这样几个类的实例对象和对象指针


A a;



B b;



A *pa1=&a;



A *pa2=&b;



B *pb=&b;

并在其后分别调用AB实例对象的g函数

(3)
反汇编测试
调用g函数反汇编后结果如下:


a.g();


004141D0
lea
ecx,[a]


004141D3
call
A::g (4112CBh)



b.g();


004141D8
lea
ecx,


004141DB
call
B::g (41154Bh)



pa1->g();


004141E0
mov
eax,dword ptr [pa1]


004141E3
mov
edx,dword ptr [eax]


004141E5
mov
esi,esp


004141E7
mov
ecx,dword ptr [pa1]


004141EA
call
dword ptr [edx]


004141EC
cmp
esi,esp


004141EE
call
@ILT+980(__RTC_CheckEsp) (4113D9h)



pa2->g();


004141F3
mov
eax,dword ptr [pa2]


004141F6
mov
edx,dword ptr [eax]


004141F8
mov
esi,esp


004141FA
mov
ecx,dword ptr [pa2]


004141FD
call
dword ptr [edx]


004141FF
cmp
esi,esp


00414201
call
@ILT+980(__RTC_CheckEsp) (4113D9h)



pb->g();


00414206
mov
eax,dword ptr [pb]


00414209
mov
edx,dword ptr [eax]


0041420B
mov
esi,esp


0041420D
mov
ecx,dword ptr [pb]


00414210
call
dword ptr [edx]


00414212
cmp
esi,esp


00414214
call
@ILT+980(__RTC_CheckEsp) (4113D9h)

调用实例对象变量的虚函数与调用一般方法一样,因此我们关心的仅仅是通过指针方式调用实例对象虚函数的过程。
我们可以看到,通过指针去访问对象的虚函数大致流程如下:
a.
获取对象地址
b.
调用对象地址的第一项所指向的地址
我们以通过pa2指针调用g函数为例,查看一下,到底程序是怎样走下去的。
首先我们获取pa2指针的地址:0x0012febc
通过内存窗口,我们获取其对应的信息:
0x0012FEBC
004251bc 00000001 00000002
我们可以猜测后面两项“00000001和“00000002为实例对象B的数据成员(可以通过获取其地址确认)。而前面一项“004251bc”,正是在调用方法前,寄存器EDX所取得的值。

EAX = 0012FEBC


EDX = 004251BC
我们继续获取“004251bc”所指向地址的值,获取以下信息
0x004251BC
0041154b 00411550 63617453
我们可以猜测,程序下一步将会跳转至地址0041154b
继续跟踪程序运行,我们可以发现程序跳转至了一下位置:

0041154B
jmp
B::g (411C90h)


00411550
jmp
B::h (411CE0h)


00411555
jmp
B::B (411C30h)

我们可以发现,0041154b地址指向的代码便是跳转至B::g()的代码。同时,我们可以发现, 004251bc地址所包含的信息,便是指向跳转至B类函数的代码。

(4)
分析
    多态实现的基础是包含信息不全的引用或者指针。所谓多态,实际上就是通过一个类的函数表实现的。所谓延迟绑定,指的便是这一个不能在编译阶段预计的调用过程。


4.
类纯虚函数的调用
(1)
试验步骤
    略,与调用虚函数一致
(2)
分析
    带有纯虚函数的类为什么不能实例化?这是因为其函数表并不完全。


三、
分析与总结
通过试验,我们可以大致勾画出类的一个组织形式:


用户系统信息:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)

一点点的激情,一点点的执着,让我一步一步的走入了自己梦寐以求的行业。从一个学校里年少轻狂的孩子,成为了一名信息安全的研发工程师。从只知道写代码,真正开始慢慢的去思考、设计和实现一种技术、一种算法、一个模块、一个软件乃至一个系统。
人生本来就该不断的追求梦想,不断的跨过一个又一个不可能穿越的鸿沟。别人看来,我很疯狂,但我笑了,人生能有几回疯?真正疯狂的人是不计后果的向前冲的,至少我还不是。我所想的,只是别人不敢想的。我所做的,只是别人不敢做的。一个一个虚无缥缈的事物,都必须是有一个一个疯狂的人逐渐的具体和完善。但愿我是这样的人,我只愿做这样的人。