上一期,我们讨论了普通函数的调用过程,如果没弄明白,看这里
今天所要讲的将是对象调用函数。
class C{
public:
int a;
int b;
int c;
void f(int t){
a = t;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
C c;
c.f(1);
c.b = 2;
return 0;
}
由于之前的普通函数的调用的基础,所以,接下来主要分析下面几个问题
对象调用如何传递对象到函数内
静态对象函数调用和普通对象函数调用有什么不同
int _tmain(int argc, _TCHAR* argv[])
{
00654D30 push ebp
00654D31 mov ebp,esp
00654D33 sub esp,0D8h
00654D39 push ebx
00654D3A push esi
00654D3B push edi
00654D3C lea edi,[ebp-0D8h]
00654D42 mov ecx,36h
00654D47 mov eax,0CCCCCCCCh
00654D4C rep stos dword ptr es:[edi]
00654D4E mov eax,dword ptr ds:[0065F0E0h]
00654D53 xor eax,ebp
00654D55 mov dword ptr [ebp-4],eax
C c;
c.f(1);
00654D58 push 1
00654D5A lea ecx,[c]
00654D5D call C::f (06510B9h)
c.b = 2;
00654D62 mov dword ptr [ebp-10h],2
return 0;
00654D69 xor eax,eax
}
通过上图可以看到,对象函数的传参和普通函数相同,使用,压栈式,不知道大家有没有注意到,这里存在两个疑点,(1)明明我是对象调用函数,编译器却作为静态函数调用模式调用。(2)编译器将c的地址传递给了EXC寄存器。我们就带着这俩个疑点继续向下看吧。
下面是C类的汇编
void f(int t){
00254420 push ebp
00254421 mov ebp,esp
00254423 sub esp,0CCh
00254429 push ebx
0025442A push esi
0025442B push edi
0025442C push ecx
0025442D lea edi,[ebp-0CCh]
00254433 mov ecx,33h
00254438 mov eax,0CCCCCCCCh
0025443D rep stos dword ptr es:[edi]
0025443F pop ecx
00254440 mov dword ptr [this],ecx
a = t;
00254443 mov eax,dword ptr [this]
00254446 mov ecx,dword ptr [t]
00254449 mov dword ptr [eax],ecx
}
通过上图可以看到,首先,函数内部将ECX寄存器内容压栈,然后初始化函数栈内容,
然后将ecx的值传入到dword ptr [this],最后完成赋值,实际在这里有个疑问,之前人们都说对象函数在编译期间将this指针作为第一个参数进行传递,如果是按照第一个参数传递,应该压栈才对,为啥这里没有压栈而实用寄存器进行传递。等下看看用静态函数看看如何吧。
static类函数
在上面的代买基础上添加了一个静态类函数,同时在调用两个函数之间通过汇编代码修改了ecx值,这个防止上一个函数调用对这部分有影响
class C{
public:
int a;
int b;
int c;
static int d;
void f(int t){
a = t;
}
static int t( ){
return d;
}
};
int C::d = 12;
int _tmain(int argc, _TCHAR* argv[])
{
C c;
c.f(1);
c.b = 2;
{
_asm{
mov ecx,10H
}
}
c.t();
return 0;
}
目前通过上图可以看到,静态函数的调用,连对象地址都没有传入。
static int t( ){
00A54370 push ebp
00A54371 mov ebp,esp
00A54373 sub esp,0C0h
00A54379 push ebx
00A5437A push esi
00A5437B push edi
00A5437C lea edi,[ebp-0C0h]
00A54382 mov ecx,30h
00A54387 mov eax,0CCCCCCCCh
00A5438C rep stos dword ptr es:[edi]
return d;
00A5438E mov eax,dword ptr ds:[00A5F024h]
}
通过上面的return的汇编代码可以看到,它是直接返回的ds:[00A5F024h]的数据,
我们重新编译可以发现&c= 0x0053FBD8
而上面ds:[00A5F024h]这个属于栈中的数据,
小结
1)学到这里,我们应该明白了成员方法调用时,在编译过程中,编译器会将对象的地址通过ecx寄存器传入到函数中,并通过地址访问对象的成员变量。
2)成员方法的调用最终都是转换为了静态函数调用,通过传递对象的地址来辨别不同类型的方法
3)实际静态成员函数也好,普通成员函数也好,调用都是通过我们上节函数调用类似,换汤不换药
4)静态成员函数调用过程中,编译器并没有传递对象的地址到函数中,这也是为什么静态成员函数无法调用普通成员变量的原因